Connascence: Una métrica para el acoplamiento


Esta métrica para la calidad del software fue inventada por Meilir Page-Jones para permitir evaluar el grado de acoplamiento entre los distintos actores en un diseño orientado a objetos. Para ello categoriza el acoplamiento en distintos tipos, sirviendo como una herramienta de análisis para medir el impacto del acoplamiento y una guía para reducirlo.

Para medir la forma de connascence existen tres factores:

connascence vector
Fuerza (de menor a mayor), grado (de bajo a alto) y localidad (de cerca a lejos).

Tipos de connascence

Los tipos de connascence se dividen en dinámicos y estáticos, yendo en gravedad de más fuertes a más débiles.

Dinámicos

Estáticos

Dinámicos

Connascence de identidad

Múltiples elementos hacen referencia a un mismo elemento.

// Both controllers knows about ViewsCounter

class ViewsCounter { ... }

class HomeController {
  constructor(private viewsCounter: ViewsCounter) {}

  index() {
    this.viewsCounter.increment()
    ...
  }
}

class PostController {
  constructor(private viewsCounter: ViewsCounter) {}

  index() {
    this.viewsCounter.increment()
    ...
  }
}

Connascence de valor

Múltiples valores en diferentes elementos deben cambiar conjuntamente.

// If PostState changes, so does Post and the test function

enum PostState {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED'
}

class Post {
  private body: string
  private state: PostState
  private title: string

  constructor(title: string) {
    this.body = ''
    this.state = PostState.DRAFT
    this.title = title
  }

  getState() {
    return this.state
  }

  ...
}

test('Post should be created as a draft', () => {
  assert(new Post('My first post').getState()).toEqual('DRAFT')
})

Connascence de tiempo

El momento de la ejecución de múltiples elementos es importante.

// The render function needs the promise to be resolved for rendering the Post list

const getPosts = async (): Promise<Post[]> => {
  return posts
}

getPosts().then((posts) => render(posts))

Connascence de ejecución

El orden de ejecución de múltiples elementos es importante.

// Changing the order of the if clauses in the main function will change its behavior

function isFizz(number: number): boolean { ... }
function isBuzz(number: number): boolean { ... }
function isFizzBuzz(number: number): boolean { ... }

function main(number: number): string {
  if (isFizzBuzz(number)) {
    return 'FizzBuzz'
  }
  if (isFizz(number)) {
    return 'Fizz'
  }
  if (isBuzz(number)) {
    return 'Buzz'
  }
  return number.toString()
}

Estáticos

Connascence de algoritmo

Múltiples elementos deben estar de acuerdo en un algoritmo en particular.

// Here the test function knows about the algorithm used for the user's password encription

test('User password should be properly encoded', () => {
  const password = 'my_completely_secure_password'
  const user = new User(password, 'John Doe')

  assert(user.getEncodedPassword()).toEqual(md5(password))
})

Connascence de posición

Múltiples elementos deben estar de acuerdo en el orden de los valores.

// Here any consumer of the getUserAddress function have to know the position of each piece of information in order to use it

function getUserAddress(user: User): (string | number)[] {
  return [
    user.getStreetName(),
    user.getStreetNumber(),
    user.getPostalCode(),
    user.getCity(),
    user.getCountry()
  ]
}

Connascence de significado

Múltiples elementos deben estar de acuerdo en el significado de ciertos valores.

// Any consumer of the getRole function needs to know the meaning of its value.
// Does 1 mean Administrator; Customer; Manager; Moderator maybe?

const user = Database.getUserByName('John Doe')

if (user.getRole() === 1) {
  ...
}

Connascence de tipo

Múltiples elementos deben estar de acuerdo en el tipo de una entidad (es común encontrarlo en lenguajes de tipado débil o dinámico).

// Which call will be correct?

function calculateAge(birthYear, birthMonth, birthDay) { ... }

calculateAge(1990, 9, 10)
calculateAge(90, 9, 10)
calculateAge('1990', '9', '10')
calculateAge('1990', 'September', '10')

Connascence de nombre

Múltiples elementos deben estar de acuerdo en el nombre de una entidad (es inevitable).

// Locally changing a property name in one point will break the class.
// Also locally changing a public property or method will break users of Customer class

class Customer {
  constructor(private name: string, private familyName: string) {}

  fullName() {
    return `${this.name} ${this.familyName}`
  }
}

Reducir el acoplamiento

Ahora que somos capaces de catalogar el grado de acoplamiento en un determinado punto de nuestro sistema identificando el tipo de connascence, podemos refactorizar para pasar a un tipo más débil, reduciendo así el acoplamiento.

Ejemplo: Paso de connascence de posición a connascence de nombre

Antes

function getUserAddress(user: User): (string | number)[] {
  return [
    user.getStreetName(),
    user.getStreetNumber(),
    user.getPostalCode(),
    user.getCity(),
    user.getCountry()
  ]
}

// Getting the city
const address = getUserAddress(user)
const city = address[3]

Después

type UserAddress = {
  streetName: string
  streetNumber: number
  postalCode: number
  city: string
  country: string
}

function getUserAddress(user: User): UserAddress {
  return {
    streetName: user.getStreetName(),
    streetNumber: user.getStreetNumber(),
    postalCode: user.getPostalCode(),
    city: user.getCity(),
    country: user.getCountry()
  }
}

// Getting the city
const address = getUserAddress(user)
const city = address.city

Ejemplo: Paso de connascence de valor a connascence de nombre

Antes

enum PostState {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED'
}

class Post {
  private body: string
  private state: PostState
  private title: string

  constructor(title: string) {
    this.body = ''
    this.state = PostState.DRAFT
    this.title = title
  }

  getState() {
    return this.state
  }

  ...
}

// Test knows about Post internal implementation
test('Post should be created as a draft', () => {
  assert(new Post('My first post').getState()).toEqual('DRAFT')
})

Después

enum PostState {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED'
}

class Post {
  private body: string
  private state: PostState
  private title: string

  constructor(title: string) {
    this.body = ''
    this.state = PostState.DRAFT
    this.title = title
  }

  isPublished(): boolean {
    return this.state === PostState.PUBLISHED
  }

  ...
}

// Test does not know about Post internal implementation
test('Post should be created as a draft', () => {
  assert(new Post('My first post').isPublished()).toBeFalsy()
})

Conclusión

Connascence nos aporta una métrica y un vocabulario común que pueden ser usados por un equipo para hablar sobre acoplamiento. Además nos será de ayuda a la hora de tomar decisiones sobre cambios en el diseño, de cara a mejorarlo.

2023-12-06
Escrito por Samuel de Vega.
Etiquetas