archivo

Archivo de la etiqueta: acoplamiento

Sucede con demasiada frecuencia que cuando se entrega una evolución de un software, ya sea en una iteración en un desarrollo iterativo incremental o en un mantenimiento correctivo o evolutivo, se realiza testing exclusivamente sobre las funcionalidades que se han desarrollado y no se comprueban otros segmentos del programa que se han podido ver afectados por los cambios realizados en el código.

En muchos casos son las prisas por cumplir plazos en el proyecto, en otros por la necesidad de solucionar una urgencia y en otros tantos porque no se piensa que determinados cambios en el código puedan provocar efectos colaterales en la aplicación.

Las posibilidades de que se produzcan este tipo de problemas dependerá mucho de la calidad de la codificación y de la sección del código que se esté tocando, ya que no es lo mismo tocar una clase con un alto acoplamiento que otra.

Para reducir el riesgo de que aparezcan efectos colaterales, es conveniente la realización de pruebas de regresión que se basan principalmente en realizar testing sobre funcionalidades de la aplicación que presentan riesgo de haber sido afectadas por los cambios. El esfuerzo necesario en realizar estas pruebas dependerá del nivel de automatización de las mismas que se tenga hasta el momento en el rango que va desde las pruebas unitarias hasta las funcionales.

Sobre las pruebas de regresión y en general sobre el hecho de que en la programación no hay que dar nada por sentado, podemos recordar la siguiente cita de Doug Linder: “Un buen programador es aquel que siempre mira a ambos lados antes de cruzar una calle que tiene un único sentido”.

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.

Hace poco me comentaba un proveedor que los resultados obtenidos con Sonar tras realizar el análisis estático del código de las aplicaciones que desarrollaban en su empresa estaban muy condicionados al framework de desarrollo y a las soluciones de generación de código que se utilizaban.

Yo le comenté que no dudaba un ápice, al contrario, del framework de desarrollo utilizado, ya que no solo respetaba las reglas de diseño y codifición establecidas en nuestro libro blanco, sino que además, facilitaba el desacoplamiento de la aplicación a nivel vertical por la existencia de diferentes capas con responsabilidades concretas y además permitía disminuir tiempos de desarrollo. Como todo en la vida es mejorable, pero el nivel que tenía era bastante alto.

Tampoco dudaba lo más mínimo de las soluciones de generación de código, es más me parece muy apropiado que se utilicen este tipo de estrategias ya que además de dar uniformidad a los desarrollos (cogidos de la mano del framework) permiten ahorrar costes de desarrollo.

Calidad y reducción de costes de desarrollo, van unidos de la mano de los frameworks y de los generadores de código. Evidentemente no aseguran nada, ni calidad ni reducción de costes, pero son la base para sustentar desarrollos de calidad a menor coste.

Lo que venía a decirle al proveedor que independientemente de que el framework y la generación de código condicionen una determinada solución, siempre existe el factor humano, ya que hay clases que se tendrán que codificar a mano (incluso subsistemas de negocio completos) y por supuesto la posibilidad de refactorizar determinadas clases determinadas por el framework, sobre todo cuando tengan un alto nivel de acoplamiento.

Es decir siempre es posible mejorar la calidad del código para mejorar la mantenibilidad del sistema. ¿Qué supone un esfuerzo? Por supuesto que sí, pero la clave, como he comentado en tantas ocasiones no consiste en intentar llegar a la perfección, ya que además de no conseguirse, aproximarse a la idea que tengamos de perfección requerirá un esfuerzo (y unos costes) que no merecen, en absoluto, la pena. En estos casos, hay que centrarse en quick wins, centrarse en clases en las que se puede mejorar la codificación y/o bien es necesario realizar una refactorización y en donde estas modificaciones permitan mejorar la calidad global de la aplicación desde el punto del vista del mantenimiento.

Sonar es una herramienta muy útil para detectar este tipo de clases, aunque también sería posible detectar clases candidatas a un alto acoplamiento, antes de desarrollar, mediante la realización de diagramas de clases en los que se refleje las dependencias entre las mismas. Cada una de esas alternativas tiene sus ventajas e inconvenientes. La gran ventaja de Sonar es que te permite medir la calidad desde múltiples puntos de vista e indicarte de manera sencilla qué clases fallan en según qué métricas, su inconveniente es que ya has codificado y todos sabemos que cuánto más tarde se detecten los problemas en la construcción, más costoso resulta corregirlos.

El acoplamiento es un concepto teórico que tiene consecuencias fatales en el mundo real. Un programa con alto nivel de acoplamiento es un programa muy difícil (y en consecuencia) costoso de mantener, en consecuencia, un software con un acoplamiento elevado tiene una deuda técnica también bastante importante.

No tener en cuenta el acoplamiento cuando se desarrolla es caer en el reverso tenebroso de la fuerza, lo mismo resulta más sencillo no tener que pensar en eso cuando se codifica, además no requiere de personal más cualificado (hacer las cosas bien para luego hacer que las cosas sean más sencillas necesita de un esfuerzo y por tanto no es nada trivial). Como ya comenté en un artículo anterior, programar pensando en que no se va a tener que volver a tocar el código es vivir ajeno a la realidad de lo que es el desarrollo de software y el ciclo de vida de los sistemas de información. ¿Puede pasar que no se tenga que tocar? Sí, pero, ¿realmente lo puedes asegurar?, ¿qué te dice tu experiencia?. Cierto es que puedes pensar: “bueno, al final le tocará el marrón a otro”, pero ¿y si al final te termina tocando a ti o a algún compañero?.

Un software fuertemente acoplado es una caja de sorpresas cuando te pones a realizar tareas de mantenimiento, lo más probable es que cuando tapes un agujero se abran dos. Al final se conseguirá sacar el mantenimiento adelante, pero con un esfuerzo, desgaste y coste bastante importante.

El uso de frameworks de desarrollo supone un primer paso para reducir el acoplamiento, pero el uso de este tipo de estrategias por sí mismas no aseguran nada, ya que aunque se “deleguen” determinadas tareas en ellos, al final las “tareas de bajo nivel” residen en el software que se implementa y que el software esté más o menos acoplado depende de las buenas prácticas, conocimientos y experiencias del programador.

A los modelos de clases no se les suele prestar demasiada atención ya que una vez obtenidos los requisitos, construido el modelo de datos y consensuada la interfaz parace que lo demás no tiene demasiada importancia, sin embargo, los modelos de clases sí que permiten ver (por lo menos a alto nivel) situaciones de acoplamiento y tomar decisiones antes de codificar que pueden reducir esta métrica. Evidentemente requiere un esfuerzo y hay que sopesar también en función de las características del sistema si se realiza ese estudio o no, habrá situaciones en las que puede merecer (y merecerá) la pena y otras en las que no (aplicaciones relativamente simples).