Archivo

Archivo de la etiqueta: cohesión

Para Bob C. Martin un software que presenta algunos de los siguientes defectos tiene un mal diseño:

1) Rigidez: Es complicado realizar cambios porque cada cambio afecta a otras partes del sistema.
2) Fragilidad: Cuando realizas un cambio, partes insospechadas del sistema empiezan a fallar.
3) Inmovilidad: Es difícil reutilizar código en otra aplicación porque no puede ser separado de la aplicación en la que se está usando.

Es importante incidir en que Martin se está centrando en aspectos de diseño y de arquitectura y no en aspectos de programación (pese a que al final estos problemas se hagan evidentes en el proceso de desarrollo y en las actividades de mantenimiento o evolución del sistema), por ese motivo los defectos que plantea están relacionados con factores como la modularidad, el alto acoplamiento, la baja cohesión y la alta complejidad ciclomática (entre otros).

Un buen diseño se valora cuando has trabajado con sistemas que no lo tienen, en los cuales realizar cualquier tarea de mantenimiento se encuentra con una resistencia que hace que los costes se disparen, sin que por ello se garantice que el producto va a pasar a producción con menos errores que los que tenía antes.

Este antipatrón se basa en la utilización dentro de un mismo sistema de diferentes “clases de propósito general”, “clases hombre-orquesta” o “clases multicasuística” en las cuales están definidos una amplia gama de comportamientos con el objeto de poder ser utilizados por otras.

También es frecuente encontrarlo en implementaciones de componentes en los que se delegan funcionalidades de otros, con el objeto de dar un amplio rango de alternativas posibles al cliente.

El problema no es dar esas facilidades, sino que el problema se encuentra en la complejidad que se le añade al código como consecuencia de las mismas (generalmente alto acoplamiento, baja cohesión, alta complejidad ciclomática, etc…).

En el antipatrón “objeto todopoderoso“, la funcionalidad de la aplicación pivota generalmente sobre una de estas clases, mientras que en la navaja suiza se trata de diferentes clases (generalmente, de utilidad) que contiene el sistema.

Un claro síntoma de que un código ha sido desarrollado por personal que todavía necesita más experiencia y cualificación es la existencia de clases que concentran gran parte de funcionalidad del sistema, de manera que el sistema pivota alrededor de las mismas (generalmente una de ellas).

Estas clases presentarán como mínimo una baja cohesión, tendrán una complejidad ciclomática superior a la media del resto de clases y probablemente mostrarán también un alto acoplamiento.

En mi organización vamos a realizar un estudio de una serie de sistemas de información (entre el 50 y el 75% aproximadamente de los mismos) con el objeto de detectar en ellos una serie de clases que pueden resultar conflictivas desde el punto de vista de la mantenibilidad.

Cualquier clase que cumpla alguna de las siguientes condiciones la consideraremos sospechosa (son solo métricas, que pueden encender determinadas alarmas, pero después resulta aconsejable revisar si existe justificación o no en que tomen esos valores) y será objeto de estudio con la finalidad de tomar una decisión sobre la necesidad de su refactorización:

1) Acoplamiento. Utilizaremos como base la métrica RFC de Chidamber y Kemerer.

RFC(clase)>=50 y RFC(clase)/Nº Métodos(clase)>=10

2) Cohesión. Utilizaremos como base la métrica LCOM4 de Chidamber y Kemerer.

LCOM4(clase)>=2

3) Complejidad Ciclomática de Thomas J. McCabe.

Complejidad Ciclomática(clase)/Nº Métodos(clase)>=10

Los valores anteriores por cada clase se obtendrán utilizando las métricas obtenidas a partir de Sonar.

Hay una reflexión de Martin Fowler que a todos nos resulta muy próxima: “…si tienes miedo de cambiar algo es que está claramente mal diseñado”.

Esto sucede muy frecuentemente cuando tenemos conciencia de que nuestro software está muy acoplado, existe una baja cohesión o las técnicas de codificación difieren mucho de ser buenas (o todas ellas juntas y alguna más).

Un software difícilmente mantenible no es ágil, pero más allá de eso, un software con mucha deuda técnica no resulta rentable.

Se podría entrar en el debate de si es ágil o no prestar atención a la arquitectura y a la codificación, en el sentido de que se puede perder la visión final que no es otra que la de diseñar un producto que satisfaga las necesidades del usuario. Sin embargo ese debate termina, cuando nos podemos encontrar con que la disminución de la mantenibilidad del software hace que el esfuerzo que se tenga que dedicar al desarrollo de cada tarea sea muy superior al que realmente necesitaría y provoque que o bien se alarguen las iteraciones o su capacidad de evolucionar el software se reduzca a mínimos.

Metodologías ágiles como la programación extrema considera fundamental la refactorización porque considera esencial la mantenibilidad del sistema.

En el anterior artículo comencé con el análisis del primero de los cuatro principios o valores que componen el manifiesto ágil. Ahora le toca el turno al segundo principio:

Principio 2: Valorar el software que funciona, por encima de la documentación exhaustiva.

La finalidad última de todo desarrollo de software es obtener un producto que satisfaga las necesidades de quienes los vayan a utilizar. Todo lo demás está uno o varios escalones por debajo.

Por tanto si una aplicación funciona tal y como quería el cliente, de manera que pueda realizar su trabajo de manera eficiente y productiva se habrá conseguido un éxito…¿o no?.

Es un éxito hoy, pero mañana puede suponer una fuente de problemas.

Entregar un software sin errores es muy complicado (no digo que sea imposible), de hecho es tal el esfuerzo que habría que hacer por parte de los que lo revisan y tal el esfuerzo que tendría que hacer por parte del desarrollador para que un software se entregue sin errores, que no resulta rentable llegar a ese extremo (salvo que se trate de un software que por sus características específicas y criticidad requiera la realización de ese esfuerzo, el cual, para que sea efectivo debe ser tenido en cuenta en el presupuesto del proyecto), esto requerirá tareas de mantenimiento correctivo, que no deberían ser muchas ni complicadas, porque si así fuera no deberíamos considerar que el software que se ha entregado funciona.

Los requisitos cambian y el software se tiene que adaptar a esos cambios, lo que hace que los sistemas de información requieran de tareas de mantenimiento evolutivo.

La realización de tareas de mantenimiento correctivo y evolutivo (como también ocurriría con el mantenimiento adaptativo y perfectivo) implica trabajar con el código fuente de la aplicación. Si la codificación no es buena: dificultad en la legibilidad y comprensión del código, alto acoplamiento, baja cohesión, el mantenimiento del sistema se puede convertir en una tortura, cuyo coste será proporcional a la deuda técnica existente.

No basta solo con que el software que se entregue funcione, la calidad del código es importante de cara a futuros mantenimientos.

En este principio se pone en una balanza el funcionamiento del sofware sobre la existencia de una documentación exhaustiva y es algo en lo que estoy totalmente de acuerdo.

El software cambia, por lo que si la documentación no cambia con él (y no me estoy refiriendo solo a la que se encuentra recogida en ficheros ofimáticos, herramientas CASE o la compuesta por los propios comentarios que aparecen en el código), tenemos documentación que cada vez servirá para menos, hasta que llegue a un punto de que más que ayudar a la comprensión del sistema, le perjudique.

Mi visión sobre la documentación (salvo las actas que se quieran elaborar de reuniones o documentación adicional que se quiera elaborar, como por ejemplo, esquemas de interfaz de usuario, etc….) es que se realice sobre herramientas CASE, ya que facilita la trazabilidad de los diferentes componentes documentales y que sea la justa y necesaria para el proyecto en el que se está trabajando, es decir, ni más, ni menos.

La documentación debe servir para que los programadores sepan qué funcionalidad es la que tienen que desarrollar, por lo que la misma debe ser concisa y sin ambigüedades. Todo lo que sea ambiguo tenderá a ser rellenado en la mayoría de los casos por la interpretación que del problema haga el programador, la cual además será errónea en la mayor parte de los casos y no porque el programador sea bueno o malo sino porque lo más habitual es que el contacto entre usuario y programador haya sido escaso o nulo, y por tanto difícilmente ha podido captar sus necesidades y sensaciones.

Por tanto, documentación, la necesaria, de calidad y actualizada (debiéndose mantener actualizada toda aquella que no se pueda obtener por ingeniería inversa). Si además a partir de ella se puede generar código pues mucho mejor.

¿Tiene la documentación alguna misión más que la de servir de apoyo al proceso de desarrollo? Si tenemos un sistema de información grande en el cual hemos podido tener diferentes proveedores trabajando y se quiere sustituir un proveedor por otro, lo mejor es que haya un soporte documental técnico que facilite la compresión del nuevo proveedor lo antes posible (también lo podría hacer interpretándolo a partir del código, pero puede resultar complejo si el mismo no es muy bueno y si el sistema es de gran tamaño).

Además, si desarrollas un producto tener ciertas partes documentadas vende (y no es ya cuestión de lo que puedas pensar como desarrollador de la utilidad de la documentación) sino de lo que pueda pensar quien te va a comprar la aplicación.

En resumen, lo primordial es que el sistema de información que se ha desarrollado se ajuste lo máximo posible a las necesidades del usuario con el menor número de errores posible pero sin olvidar que también debe ser desarrollado con un código de calidad y se ha debido generar una documentación que debe ser la mínima necesaria para la realización y comprensión del sistema, que también debe ser de calidad y actualizarse, salvo aquellas partes que se puedan obtener de forma rápida y sencilla mediante ingeniería inversa.

Continuemos con el análisis de los principios del manifiesto ágil.

Se codifica, se construye la aplicación, pero en demasiadas ocasiones se tiene en cuenta como fin único la entrega del proyecto, dejando al margen o dedicándole menos importancia a una programación pensando en futuros mantenimientos.

La dificultad de los mantenimientos depende de muchos factores y algunos de ellos están por encima en importancia de una codificación clara y de calidad, como por ejemplo la búsqueda de una alta cohesión y un bajo acoplamiento, lo cual no debería ser una excusa para olvidarnos de este asunto ya que no se trata solo de que se pueda comprender lo que se ha programado (que no es poco) sino que un código poco cuidado probablemente influirá en una mayor complejidad ciclomática de la aplicación (además de a la mayoría de las variables que determinan la deuda técnica de la aplicación) dificultando, en consecuencia, a la mantenibilidad.

Generalmente nos solemos acordar de todo esto cuando toca realizar el mantenimiento del sistema de información y nos encontramos con que la forma en que se ha codificado hace que se requiera un mayor esfuerzo y en consecuencia coste. Por este motivo importa y mucho como se codifica.

El problema de todo esto es que resulta complicado detectar este tipo de problemas en tiempo de desarrollo ya que obliga a estar encima y revisar el código, lo que puede hacer que casi cueste más el collar que el perro. Por tanto, tenemos que utilizar otras estrategias que faciliten la localización de estos problemas, como es por ejemplo estudiar variables que se pueden incrementar indirectamente por una mala programación (se puede realizar una revisión periódica de las métricas que devuelve Sonar) y complementarlo, si es posible, con la revisión, también periódica, de una muestra de clases de la aplicación.

Siendo realista, veo complicado que una empresa de desarrollo, salvo que las deficiencias encontradas en el proceso de verificación interno sean serias, se pongan a rehacer a módulos ya codificados, pero por lo menos si se aplican estas políticas se puede reconducir la tendencia en el desarrollo y que el proyecto se encuentre dentro de unos umbrales de calidad aceptables (los cuales en algunos casos podrán ser exigidos y verificados por el cliente y provocar un rechazo en la entrega y la consiguiente necesidad de refactorizar partes de la aplicación lo cual se volverá en contra del proveedor ya que tendrá que sufrir en sus carnes las consecuencias de unas malas prácticas.

Martin Fowler es un importante y reconocido autor especialista en ingeniería del software y en el proceso de desarrollo (particularmente en el campo de la refactorización) el cual ya he tenido la oportunidad de citar en un artículo sobre una regla de CheckStyle que no comprendía su significado.

Steve C. McConnell es otro reputadísimo autor y especialista en ingeniería del software, tanto es así, que durante años fue considerado junto a Linus Torvalds y Bill Gates unas de las personas más influyentes en el negocio del desarrollo de software. En la actualidad posee una empresa llamada Construx Software que se dedica a consultoría y formación sobre buenas prácticas en desarrollo de software.

Ambos autores son responsables de las siguientes citas relacionadas con la comprensión del código:

Martin Fowler (traducción libre): “Cualquiera puede escribir código que un ordenador pueda entender. Los buenos programadores son aquellos que escriben código que los humanos puedan entender”.

Steve McConnell (traducción libre): “El buen código es su mejor documentación. Cuando vayas a escribir un comentario, pregúntate, ¿cómo puedo mejorar el código para que este comentario no sea necesario?”.

Sobre este tema ya publiqué una cita que se atribuye a Martin Golding en unos casos y a John F. Woods en otros que complementa perfectamente a éstas.

Como he comentado en otras ocasiones, hacer las cosas bien, tarde o temprano, marca la diferencia.

Cuando se habla de programación orientada a objetos se dice que un buen diseño es aquel que consigue una alta cohesión y un bajo acoplamiento. Como es lógico, estoy de acuerdo, no obstante de los dos conceptos la cohesión, tal vez por entenderse menos su utilidad, es el hermano pobre, es decir, si se tiene en cuenta alguno de los dos (algo que desgraciadamente no ocurre demasiado, ya que no se suele prestar suficiente atención a la calidad del diseño y de la codificación) se suele echar más en cuenta al acoplamiento, además de ser más inteligible por ser más evidentes los problemas existentes cuando el acoplamiento es alto.

La cohesión mide la relación entre los distintos elementos que componen una clase: atributos y métodos. Una baja cohesión da lugar a las denominadas clases tutti frutti, clases que pretenden hacer muchas cosas y diferentes.

Cuando se tienen clases con baja cohesión implica principalmente:

- Falta de comprensibilidad: La clase no sirve a un aspecto concreto, sino a muchos. Cuando pensamos en un objeto lo hacemos entendiendo que sus partes son necesarias para que exista su todo (es decir, las partes por sí solas tienen poca utilidad y es mediante su relación cuando dan entidad al objeto y hacen que en su conjunto (el todo) valga más que la suma de las partes por separado). Cuando tenemos clases cuyos elementos forman un todo, pero ese todo no es identificable con nada, nos encontraremos probablemente con clases que tienen una baja cohesión, con métodos que no guardarán relación con otros muchos de la misma y que servirán a propósitos distintos. Habrá que tener bien documentada la clase para mantenerla posteriormente, porque si la coge un equipo de desarrollo distinto o ha pasado mucho tiempo desde que se implementó resultará complicado entender para qué sirve y qué utilidad tienen muchos métodos y atributos de la misma.

- Dificulta la mantenibilidad: Además de por la disminución de la comprensibilidad de la clase, al tener más código del que debería tener, se incrementará en consecuencia la complejidad ciclomática de la misma y por tanto será más costoso realizar cambios.

- Dificulta la reutilización: Cuando tenemos una baja cohesión quiere decir que hemos modelado una realidad rara, un híbrido de muchas cosas. Se ha implementado una clase para algo muy específico y lo concreto pocas veces es reutilizable.

- Suelen tener un acoplamiento alto (en comparación a una cohesión baja): Tiene su lógica ya que la clase tendrá más métodos que si los diferentes objetos reales que están dentro de la misma se hubieran encapsulado en sus correspondientes clases. Más métodos implican por regla general llamadas a más métodos de clases y paquetes externos y en consecuencia un mayor acoplamiento.

- Tendentes a sufrir modificaciones: Como estas clases son híbridos de varias la probabilidad de tener que sufrir operaciones de mantenimiento crece (tanto como clases distintas pudieran contener en su interior), lo cual tiene su importancia sobre todo si tenemos en cuenta que este tipo de clases son más complicadas de mantener.

Una forma de medir la cohesión la tenemos con la métrica LCOM4, propuesta a partir de la métrica inicial LCOM1 de Chidamber y Kemerer. Esta métrica forma parte del conjunto de las mismas que calcula Sonar haciendo análisis estático de código.

LCOM4 es otra métrica que utiliza Sonar y en la cual el concepto también es independiente de la existencia del producto.

Esta métrica mide la falta de cohesión de una determinada clase (forma parte de un conjunto de métricas que cogen su nombre de las iniciales de Lack of Cohesion of Methods). Antes de entrar a analizar cómo funciona esta métrica, es de interés mencionar aunque sea en líneas generales, qué es la cohesión y para qué sirve.

La cohesión mide la especialización (o la proximidad al principio de responsabilidad única) de una determinada clase o lo que es lo mismo cómo de relacionados están los distintos elementos (atributos y métodos) que la componen. Se entiende que clases más especializadas son más mantenibles. Esto es fácil de apreciar cuando por ejemplo miramos el código de una clase cohesionada y el código de una clase Tutti Frutti, en el primer caso veremos que la clase describe un objeto y su comportamiento, considerando dicho objeto como algo “atómico”, en el segundo caso nos podremos encontrar (exagerando mucho) clases que plancharán la ropa, jugarán al fútbol, te hacen un Cola Cao y son capaces de mantener una charla contigo sobre metafísica. Evidentemente, por muy complejo que sea el primer tipo de clases, probablemente el comportamiento, inteligibilidad, mantenibilidad, etc… de la segunda se vea afectado en sentido negativo.

Existen distintos grados de cohesión, según la forma en qué estén relacionados sus elementos. En el párrafo anterior me centré en la de carácter funcional, que es la que me parece más interesante.

¿Cómo funciona LCOM4? Cálcula un valor para una determinada clase, si ese valor es mayor que uno, la clase es sospechosa de falta de cohesión (cuento mayor sea dicho valor, menos cohesión tendrá). Lo que hace Sonar es indicarte el porcentaje de clases respecto al total que tienen un LCOM4 mayor que uno, así como calcularte el LCOM4 medio. Como en el resto de métricas de Sonar, la clave está en definir umbrales, es decir, qué porcentaje de clases con LCOM4 mayor que uno se considera aceptable y cuál es el máximo valor de LCOM4 admisible para las clases. Puede haber casos donde se pueda justificar superar dichos umbrales (suponiendo que dichos umbrales se hayan depurado con la experiencia), en ese caso, el equipo de desarrollo deberá explicar los motivos y ser coherentes si quiere que se le acepte la entrega.

Para el cálculo del LCOM4 de una clase básicamente lo que hace es medir el número de relaciones diferentes que se establecen entre métodos, teniendo en cuenta que un método se considera relacionado con otro si acceden a un atributo común de la clase o si uno llama a otro. Para el cálculo hay métodos que no se tienen en cuenta como por ejemplo los constructores. Tampoco se tienen en cuenta los accesores.

A través de este enlace podréis acceder a un ejemplo bastante bueno para comprender cómo se calcula esta métrica.

Por tanto, la cohesión es un factor a tener en cuenta junto al acoplamiento (entre otros factores) ya que condiciona la mantenibilidad y deuda técnica de los proyectos de desarrollo de software.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 3.199 seguidores