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?
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.