Hace poco tiempo hablamos de Connascence. En el post comentamos que el Connascence es una métrica de calidad software y una taxonomía (forma de clasificar) el acoplamiento entre componentes software. Por ello es una buena herramienta para tomar decisiones de diseño sobre todo cuando estamos refactorizando.
En el libro de Refactoring se habla de «code smell» como una manera de identificar la partes del código que pueden tener problemas y que sería bueno refactorizar. El inconveniente de los code smells es que son subjetivos y pueden variar según el lenguaje de programación, el desarrollador. Por ello podemos ayudarnos del connascence para identificar los code smell y así empujar el refactoring tomando decisiones basadas en el connascence.
Para ilustrar esto, lo ideal es tener en cuenta esta gráfica.
En ella vemos como lo ideal es tomar las decisiones intentando minimizar el connascence o acoplamiento yendo lo más hacia la izquierda posible.
Así que vamos a ilustrar todo esto con ejemplos.
Minimizando el Connascence of Position
Por ejemplo si tenemos este método que devuelve el ancho y el alto de una imagen
function calculateImageSize($url){
$width = ...
$heigh = ...
return [$width, $height];
}
list($width, $height) = calculateImageSize('url/img');
Vemos que tenemos un Connascence of Position ya que hemos decidido que primero de devuelva el ancho y luego el alto. Es decir, la posición de los valores retornados es fija y a menos que pusiésemos un comentario, nadie viendo la firma del método podría saber si se devuelve anchoxalto o altoxancho.
Para minimizar este connascence, quizás sería bueno crear una clase ImageSize así:
Class ImageSize
{
private $width;
private $height;
public funcion __construct ($width, $height){
$this->width = $width;
$this->height = $height;
}
public function width(){
return $this->width
}
public function height(){
return $this->height
}
}
function calculateImageSize($url){
$width = ...
$heigh = ...
return new ImageSize ($width, $height);
}
$imageSize = calculateImageSize('url/img');
Con esto estamos eliminando el connascence de Position para convertirlo en un connascence de Name ¿algo mejor no?
Minimizando el Connascence of Execution
Vamos con otro ejemplo, imaginemos que tenemos una clase que itera sobre los elementos de un repositorio, pero para hacerla funcionar tenemos que indicarle cual es el repositorio y que método tiene que usar, algo así:
$reader = new ItemRepositoryIterator();
$reader->setRepository(new VehicleRepository);
$reader->setMethodName('findAll');
$vehicle = $reader->read();
Vemos que cuando instanciamos la clase ItemRepositoryIterator
no podemos usarla hasta que no seteemos el repositorio y el método, eso es un Connascence of Execution en toda regla.
La solución a este connascence pasa por usar inyección de dependencias, así cuando hagamos el new de ItemRepositoryIterator
podremos usar la clase sin problemas. La solución quedaría algo así:
$reader = new ItemRepositoryIterator(new VehicleRepository, 'findAll');
$vehicle = $reader->read();
Con esto hemos eliminado el connascence de ejecución, ¿qué os parece?
Otro ejemplo de esto sería por ejemplo la típica clase Mailer.
$mailer = new Mailer;
$mailer->setTo('a@email.com');
$mailer->setFrom('my@email.com');
$mailer->setsubject('a subjet');
$mailer->setBody('a body');
$mailer->send();
La idea es intentar que las clases sean funcionales desde el momento que se crean y no haya que setear valores después de instanciarlas. Aquí tenemos un claro ejemplo de Connascence
Minimizando el Connascence of Algorithm
Este connascence es más típico de lo que pensamos, se da sobre todo cuando varias clases tienen que ponerse de acuerdo en que algoritmo utilizar. La idea es que si cambiamos el algoritmo minimizar el número de cambios. Vamos con un ejemplo
def write_data_to_cache(data_string):
with open('/path/to/cache', 'wb') as cache_file:
cache_file.write(data_string.encode('utf8'))
def read_data_from_cache():
with open('/path/to/cache', 'rb') as cache_file:
return cache_file.read().decode('utf8')
Como vemos aquí tenemos un acoplamiento por el algoritmo utilizado (UTF8) ya que para cambiar dicho algoritmo sería necesario que buscásemos donde esta la cadena UTF8 y tendríamos que remplazarla suponiendo que no nos dejamos ningún sitio sin cambiar.
Para eliminar ese connascence podemos hacer lo siguiente:
def write_data_to_cache(data_string):
with open('/path/to/cache', 'wb') as cache_file:
cache_file.write(self.Algorithm.encode(data_string)
def read_data_from_cache(codificationAlgorithm):
with open('/path/to/cache', 'rb') as cache_file:
return self.Algorithm.decode(cache_file.read())
Encapsular el algoritmo en una propiedad de la clase, así con solo cambiar dicha propiedad cambiaríamos todos los lugares donde se utiliza.
Minimizando Connascence of Meaning
También podemos minimizar este connascence, aunque tendremos que poner sobre la balanza si reducir tanto el connascence será productivo para nuestra aplicación o nos acabará dando dolores de cabeza. Por eso vamos a verlo con un ejemplo.
public function getSSN() {
if ($this->$ssnIsMissing) {
return "999999999";
} else {
return blah_blah_blah;
}
}
$ssn = getSSN();
if ($ssn == "999999999") {
echo "error";
die();
}
Como podemos ver tenemos un acoplamiento para «999999999», todos los componentes deben conocer que cuando se devuelve «999999999» es un error y tenemos que manejarlo, la solución podría ser:
public function getSSN() {
if ($this->ssnIsMissing) {
throw new SSNMissingException();
}
return blah_blah_blah;
}
Quizás la solución no sea elegante, pero conseguimos reducir el connascence
Conclusiones
Como hemos visto tanto en este post como en Connascence, otra manera de hablar de acoplamiento y cohesión el connascence nos ayuda tomar decisiones cuando estamos refactorizando. De la misma forma también nos sirve a la hora de elegir un camino u otro cuando estamos refactorizando, diseñando y/o escribiendo código.
La idea del connascence es tener una base sobre la que tomar decisiones, no significa que tengamos que seguirlo al pié de la letra. Como hemos comentado antes tendremos que poner sobre la balanza si reducir el connascence será productivo para nuestra aplicación o nos acabará dando dolores de cabeza.