Connascence

El otro día acudí a una interesante charla de Alfredo Casado @alfredocasado sobre Connascence que organizó el meetup de Software Craftsmanship Madrid. La charla muy bien llevada por Alfredo, estuvo llena de ejemplos reales de código y de consejos prácticos. Cómo he detectado que este término es desconocido para mucha gente, he decidido escribir este artículo resumiendo la charla de Alfredo aunque utilizando otros ejemplos de código que he ido encontrando.

¿Que es connascence?

Connascence es una métrica creada por Meilir Page-Jones en su libro What Every Programmer Should Know about Object-Oriented Design la cual nos permite hablar de la dependencia entre componentes software. Dos componentes software tienen una relación de connascence si para cambiar uno de ellos es necesario cambiar el otro para que su funcionamiento siga siendo correcto. Los términos connascence y acoplamiento son muy similares, hasta el punto de que Kent Beck escribió en Twitter que para él tienen la misma definición.
Connascence nos dota de un vocabulario para poder hablar de relaciones de dependencia entre componentes software, pudiendo hablar de diseño de una manera más avanzada. Conocer los tipos de connascence nos puede ayudar a mejorar nuestro código, ya que nos ayuda a reconocer el acoplamiento y nos da pistas sobre la dirección hacia dónde avanzar para disminuirlo.
Una relación connascence puede tener diferente fuerza, grado y localización.
– Será más fuerte cuando más número de cambios requiera modificar los elementos implicados, o dicho de otra manera cuanto más complejo es cambiar el código más fuerte es la relación de connascence.
– El grado está relacionado con la intensidad del impacto que requiere un cambio. Es menor el grado si afecta a dos métodos que si afecta a cientos.
– La localización depende de la distancia entre los componentes sofware que tiene una relación de connascence, si son del mismo método, clase, contexto, etc. Cuanto más alejados están, más dañina es su relación de connascence.

Tipos de connascence

Hay distintos tipos de connascence y se agrupan en dos grupos: estáticos y dinámicos. Los estáticos se pueden observar mirando el código y los dinámicos en tiempo de ejecución. Los tipos de connascence de menos dañinos a más son:

Connascences estáticos

Connascence de nombre

Se da cuando dos entidades están de acuerdo sobre un mismo nombre, y el cambio de este nombre require el cambio en ambas entidades. Un ejemplo de connascence de nombre sería la relación entre una clase a la que definimos con un nombre y todas las variables instancidas que usan esta clase y por lo tanto este nombre. Este connascence es inevitable cuando escribimos código.

Connascence de tipo

Se da cuando dos entidades están de acuerdo sobre un tipo. Esto se da por ejemplo cuando declaramos un método que recibe un parámetro de una clase concreta. Si cambia la clase del parámetro deberán cambiar todos los lugares donde se llama a este método pasandoles un objeto del nuevo tipo para que siga funcionando de manera correcta.

Connascence de significado

Es cuando varios componentes están de acuerdo sobre el significado de un valor determinado. Por ejemplo en el siguiente método el parámetro recivedRequest tiene un tratamiento diferente en función de si el atributo AuthType contiene la cadena «EAP». Hay un acuerdo acerca del significado de la cadena «EAP» en este contexto.

public RadiusPacket processRequest(final RadiusPacket request) {
  String authType = request.getAuthType();
  if (authType != null && authType.equals("EAP")) {
    return eapHandler.executeEAPProcess(request);
  } else {
    return otcHandler.executeVerification(request);
  }
}

Este connascence podría disminuir su nivel a connascence de tipo si se utilizara polimorfismo para crear dos subclases de radiusPacket como en el siguiente fragmento de código:

public RadiusPacket processRequest(final RadiusPacket request) {
  if (request instandeof EAP) {
    return eapHandler.executeEAPProcess(request);
  } else {
    return otcHandler.executeVerification(request);
  }
}

Connascence de posición

Cuando dos componentes tienen que estar de acuerdo en un orden determinado. Por ejemplo en el siguiente código tenemos un método con 3 parámetros. Si el orden de estos parámetros cambiase habría que cambiar de manera necesaria el orden de los parámetros en las llamadas de este método.

process("11231", 1, 3);

Para disminuir el nivel de connascence se podría cambiar los tres parámetros por un parámetro de una clase que los incluyera como atributos, con lo que pasaríamos a tener connascence de tipo.

process(new ProcessRequest("11231", 1, 3));

Connascence de algoritmo

Es cuando varios componentes están de acuerdo acerca de usar un algoritmo. Hace poco me encontré con un ejemplo real de este tipo de connascence. Resulta que se probó por primera vez un servicio que de manera centralizada gestionaba el reporting de otros servicios que utilizaban para ello colas SQS de Amazon, a las que otros servicios enviaban eventos serializados utilizando Jackson para esta serialización. Algunos servicios estaban utilizando la librería Gson para serializar los eventos provocando fallos en la deserialización. Era un caso de connascence de algoritmo ya que todos estos componentes dependían de un algoritmo de serialización específico y si no lo utilizaban fallaban. Este conascence se podría haber evitado creando una librería compartida por todos los servicios, que incluyese un método común para la serialización que encapsulase el algoritmo utilizado.

Connascence dinámicos

Connascence de ejecución

Es cuando el orden de ejecución de dos fragmentos de código es importante. En el siguiente fragmento de código podemos ver el uso de la librería activiti que permite ejecutor flujos Bmp. Para ejecutar un flujo Bpm primero se despliega el flujo y después inicia. Si no se sigue este order este fragmento no funcionaría por lo que hay un connascence de ejecución, ya que hay un acuerdo acerca del orden. Una manera de evitar esto podría llevarse a cabo modificando la librería para que un flujo fuera desplegado si no lo ha sido antes al hacer la llamada para iniciarlo.

repositoryService.createDeployment()
        .addInputStream(deployProcessRequest.getActivitiProcessDefinition().getFile(),
                remoteResourceLoader.loadResource(deployProcessRequest
.getActivitiProcessDefinition()
                        .getFile()))
        .addInputStream(deployProcessRequest.getActivitiProcessDefinition().getImage(),
                remoteResourceLoader.loadResource(deployProcessRequest
.getActivitiProcessDefinition()
                        .getImage()))
        .enableDuplicateFiltering()
        .name(name).deploy();

ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processKey,
createProcessVariables(initProcessRequest));

Connascence de tiempo

Es cuando el tiempo de ejecución de varios componentes es importante. Esto se da por ejemplo cuando una llamada a un método que consulta algún estado en la base de datos, depende de que la actualización previa de este estado haya terminado y si no lo ha hecho falla. Este connascence se da de forma típica en sofware que maneja problemas de concurrencia.

Connascence de valor

Ocurre cuando varios valores tienen que cambiar juntos. Esto ocurre por ejemplo cuando tenemos una cadena de caracteres repetida a lo largo de nuestro código y si el valor de una de ellas cambia debemos cambiar el valor del resto de apariciones para que siga funcionando. Este ejemplo concreto se podría mejorar creando una constante a la que se hace referencia desde todos las repeticiones, unificando el lugar donde está la cadena y haciendo más sencillo un cambio.

Connascence de identidad

Ocurre cuando varios componentes tienen que hacer referencia al mismo objeto. Esto se daría por ejemplo si varios componentes que usan la misma base de datos necesitasen hacer referencia a una entidad usando su identificador que sería contante. Si esta entidad no existiese o cambiase de identificador el sistema dejaría de funcionar.

Deja un comentario