archivo

Archivo de la etiqueta: mantenibilidad

Que existan restricciones en el proyecto no quiere decir que sean innegociables. ¿Son más importantes las restricciones que el producto final? No lo debería ser, salvo circunstancias excepcionales como el hecho de que un producto tenga que estar para una fecha concreta y/o que el presupuesto no haya forma de ser ampliado.

Si las restricciones son innegociables puede que sí lo sea el alcance final. Si no llegamos, por lo que menos que las funcionalidades más importantes del sistema funcionen de manera adecuada y el resultado sea una aplicación mantenible (con una deuda técnica acorde a las características del producto y al contexto del proyecto).

En cualquier caso existen opciones para ser flexible (que serán más o menos amplias en función del proyecto y las circunstancias).

No obstante es importante tener en cuenta que los cambios en los parámetros del proyecto y/o del sistema no deben ser fruto de una baja productividad o de un trabajo negligente sino como consecuencia de que en el propio proceso de desarrollo se ha llegado a la conclusión de que la complejidad del sistema es mayor de la prevista, son necesarias más iteraciones o bien se han dado circunstancias que han afectado al correcto desarrollo del sistema (entre otras muchas causas posibles).

La baja productividad o la negligencia impactarán y afectarán probablemente a las restricciones. Cuando he dicho que el cambio no debe ser fruto de ellas quiero decir que no hay barra libre para ese comportamiento y resultados, si lo has hecho, lo pagas, habrá que analizar cómo, pero no es lógico que el cliente cargue con la responsabilidad de un mal desempeño del proveedor (como tampoco sería lógico al revés) por lo que la solución no pasa por el «aquí no ha pasado nada» y vamos a negociar más dinero o el alcance, sino por el «tu las has fastidiado y tienes que tener responsabilidad sobre lo que has hecho».

Como afectan también a las restricciones, si fuera posible flexibilizarlas (sobre todo los plazos que será realmente el parámetro que se deba mover, ya que el coste y la calidad no deberían verse afectados) hay que hacerlo por el bien del producto final. Los desarrollos requieren su tiempo, menos del tiempo necesario, pondrá el riesgo la calidad del producto tanto en lo que a mantenibilidad se refiere como a la cantidad de incidencias que llegarán a producción.

Que el software se deteriora conforme se va evolucionando es un hecho a partir del momento en que el sistema tiene implementadas el conjunto de funcionalidades que el usuario realmente necesita (el problema de esto es que casi siempre resulta complicado saber cuándo se llega a este punto y que incluso llegando a él habrá funcionalidades adicionales a las estrictamente necesarias que también se habrán implementado).

Este deterioro es aplicable a nivel funcional (se implementarán nuevas funcionalidades muchas de las cuales serán innecesarias, lo que hará más complejo el uso del sistema, introducirá nuevos errores y provocará probablemente efectos colaterales) y técnico (antipatrones ancla de barco y lava seca, incremento de la complejidad, etc…).

Hablo en términos generales, ya que incluso en estos casos es posible mejorar el sistema en ambos aspectos.

Ahora bien, me parece muy interesante poneros la siguiente reflexión de Dave Thomas, ya que la deuda técnica es una resistencia que tiene el software pero de cara a su evolución y en ese sentido es importante tenerla en cuenta, no obstante la deuda técnica no actúa si el software está en reposo (eso de que no actúa también es relativo ya que un sistema con una deuda técnica alta es serio candidato a dar muchos problemas) si vemos dicho concepto desde el punto de vista de la mantenibilidad: «La métrica del coste del cambio empieza a correr cuando tomas una decisión. Si no tomas una decisión, entonces no hay nada que cambiar y la curva contínúa plana».

Por tanto, la degradación no debería considerarse función del tiempo sino de la evolución. Sin embargo se asocia generalmente con el tiempo y tiene una cierta lógica ya que la evolución es consecuencia de actuaciones que se realizan en el sistema conforme avanza el tiempo.

El artículo publicado ayer en el que comentaba los beneficios de la refactorización, lo contextualicé con una cita de Ron Jeffries en la que venía a decir que una aplicación bien diseñada y programada de base podría convertirse en un elemento complicado de mantener si no se revisaba con la frecuencia necesaria el código, sobre todo en contextos iterativos, evolutivos o de mantenimiento del sistema.

Y es que queramos o no, si no se hace esta tarea el programa se convierte en un conjunto de parches cada uno de ellos con el sello de su programador y cada vez resulta más costoso de mantener a la vez de que se incrementarán las posibilidades de efectos colaterales.

Jeff Atwood realiza la siguiente cita que complementa perfectamente a la de Jeffries: «Si no te paras de vez en cuando (frecuentemente, en realidad) en revisar y limpiar el código que ya has escrito, terminará convirtiéndose en una gran y descuidada bola de código».

No es necesario poner ejemplos sobre esto porque la mayoría de vosotros habréis tenido la oportunidad de padecer y sentir las consecuencias de código de baja calidad y/o parcheado. Pese a esto y como ya comenté en el artículo de ayer no se le presta en la mayoría de los casos la atención que se merece a la refactorización, algo que resulta todavía más sangrante cuando el producto que desarrollas es para tu organización ya sea de uso interno o para comercializarlo ya que en estos casos no se tiene la excusa de que el cliente no valora este tipo de esfuerzos.

Este antipatrón es una mala práctica de programación y se llega a él a través de la implementación de métodos que intentan ser lo suficientemente flexibles como para ser adaptado su comportamiento a multitud de circunstancias, sobrepasando el umbral de una mantenibilidad adecuada del mismo (elevado número de parámetros donde la mayoría de ellos a veces se usan y otras no, alta complejidad ciclomática, etc…).

Presenta una analogía con el antipatrón «objeto todopoderoso«, pero teniendo como protagonista a un método y no a una clase.

¿Cuál es el precio de la calidad del software? Resulta complicado ponerle un valor a algo que para cada uno tiene un significado diferente. Para mi la calidad del software puede significar una cosa y para ti otra y probablemente ambos tengamos razón.

Sin embargo, probablemente estaremos de acuerdo en que la calidad estará muy relacionada con el cumplimiento de las expectativas del usuario (y ese aspecto está por encima de la ejecución técnica del desarrollo), con el hecho de que su deuda técnica y mantenibilidad (estos aspectos sí que son puramente técnicos) superen un umbral acorde al presupuesto del proyecto, los plazos y todas las contingencias que se han producido en su desarrollo y también estará relacionada a la cantidad de errores críticos o bloqueantes que lleguen a producción.

También estaremos de acuerdo en que la calidad requiere método o proceso, que podrá ser más artesanal o más reglado, pero método o proceso al fin y al cabo. Requerirá iterar versiones en las cuales el feedback del usuario permitirá ir adaptándolo cada vez más a sus expectativas. También requerirá de un equipo de proyecto experimentado (por lo menos en número suficiente) que crea en la calidad del software, que tenga el enfoque en ese objetivo y no en simplemente quitarse el marrón lo antes posible.

La calidad vale dinero, sin embargo su ausencia o su falta vale mucho más.

Sobre este aspecto, la siguiente reflexión de Tom de Marco y Tim Lister resulta muy esclarecedora: «La calidad es gratis pero solo para aquellos que están dispuestos a pagar un precio muy alto por ella».

En el entorno de personas con las que trabajo existen serias dudas de que la metodología de desarrollo guiado por las pruebas o Test-Driven Development, sea ágil, en el sentido de que entienden que si se aplica de manera ortodoxa o si bien las pruebas unitarias se desarrollan después, se pierde capacidad de trabajo efectivo por parte del equipo de proyecto.

Nunca he participado en un proyecto que siga esta metodología y en los desarrollos en los que he trabajado la importancia que se le da a las pruebas unitarias es muy escasa, por lo que no puedo aportar mi experiencia para opinar sobre las bondades o no de esta metodología, de manera que me tendré que centrar en lo que he leído sobre ella.

No obstante, hay que tener en cuenta una cosa. Agilidad no es lo mismo que desarrollar más rápido o por lo menos no es su objetivo principal, ya que si bien es cierto que la aplicación de metodologías ágiles, eliminan muchos aspectos que son prescindibles en un proyecto de desarrollo de software y por lo tanto pueden optimizar tiempos de desarrollo, el objetivo último de las mismas es conseguir productos software de calidad, que proporcionen satisfacción al cliente, dentro de unos plazos y presupuestos definidos (siempre y cuando, ambos sean realistas y acordes a la naturaleza del proyecto y al nivel de calidad que se espera del producto software), es decir, que no se produzcan los factores que derivan en la crisis del software.

El desarrollo guiado por las pruebas, fue introducido (o reintroducido) por Kent Beck, creador de la metodología de programación extrema y en cierto modo la segunda es una consecuencia natural de la primera, si bien ambas pueden utilizarse sin problemas por separado.

La metodología TDD a grandes rasgos es fácil de exponer:

– Se construyen las pruebas unitarias (o se puede extender si se desea el concepto a otros tipos de pruebas de más alto nivel, siempre y cuando sea automatizable su ejecución).

Como estos casos de prueba están construidos antes desarrollarse el componente software sobre el que se va a trabajar, van a fallar a la primera. De hecho se considera necesario verificar que fallan porque si no es así, querría decir que el test está más construido (¿por qué no?) o que el componente software ya está desarrollado (en proyectos donde se trabaja con sistemas de mediana o gran envergadura y con un número de desarrolladores alto, puede pasar que el componente o módulo ya se haya implementado previamente, bien porque le ha hecho falta a un compañero para otra funcionalidad o bien porque no se ha verificado que efectivamente ese componente ya estaba desarrollado).

Para la construcción de los casos de prueba, los desarrolladores deben basarse en las especificaciones indicadas en los casos de uso o en las historias de usuario. Ahí radica la importancia que tiene la construcción de las pruebas unitarias antes del desarrollo del componente, ya que implica un mayor enfoque en los requisitos, una mayor necesidad de comprender la funcionalidad y comportamiento deseado, es decir, implica comprender el problema, conocer los resultados esperados, antes de empezar a desarrollarlo.

La ventaja de desarrollar las pruebas antes de implementar los componentes es la indicada en el párrafo anterior, es decir, enfrentarse y comprender el problema antes de empezar a resolverlo.

Si las pruebas unitarias se construyen después, no estaríamos hablando de TDD, eso es importante, por lo que lo repito, si las pruebas unitarias se construyen después se trata de otra estrategia de desarrollo de software, no de desarrollo guiado por las pruebas.

Pese a que como he comentado, no se ha fomentado en los proyectos en los que he participado la construcción de pruebas unitarias, soy de la opinión de que es favorable tener una buena cobertura de código, por lo que la existencia de pruebas unitarias, antes o después de implementar los componentes, permite detectar de manera rápida la existencia de efectos colaterales o desarrollos erróneos de métodos o clases.

¿Por qué no lo hemos fomentado? Es una suma de varios factores, por un lado pese a que entendemos los beneficios que puede proporcionar, no tenemos del todo claro su retorno de la inversión, pese a que es objetivo que la deuda técnica se reduce en proporción a la cobertura, por otro lado, los proveedores con los que hemos trabajado tampoco utilizan esta práctica en sus desarrollos. Por tanto y en resumen, la falta de experiencia nuestra y de los proveedores hace que nos más dar el paso y prioricemos otros aspectos en el proceso de desarrollo y en la calidad del diseño y de la codificación.

– A continuación se construye el componente software que debe verificar las reglas de funcionamiento definidas en las pruebas.

Una vez desarrollado, se pasa el test y si funciona adecuadamente, se pasa de nuevo todo el juego de pruebas, si hay errores, se vuelve a revisar el componente, si todo ha ido bien, se pasa a la siguiente fase.

– Una vez que se tiene un software que verifica las pruebas implementadas, se procede a su refactorización.

El objetivo de la utilización de esta técnica es evidente, desarrollar software con el menor número de fallos posible, reduciendo además los errores debidos a efectos colaterales en el desarrollo, respetando también la calidad de diseño y de codificación para favorecer la comprensión y capacidad de mantenimiento del software.

Se sabe que cuanto más tarde se detecten los errores más esfuerzo se requiere para solucionarlo, esta técnica pretende disminuir el riesgo por la presencia de errores de ese tipo, además de facilitar un código donde sea mucho más fácil trabajar, tanto para corregir esas incidencias como para contruir nuevas funcionalidades o modificar, si fuera necesario (el software es de naturaleza adaptativa), las ya implementadas.

Espero poder probar más pronto que tarde esta metodología en alguno de los proyectos en los que participo y poder verificar personalmente las ventajas que tiene la utilización de esta estrategia de desarrollo.

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.

Martin Fowler realiza la siguiente reflexión (traducción libre): «Una de las cosas que he estado tratando de hacer ha consistido en buscar las reglas más simples que sustenten un diseño bueno o malo. Creo que una de las reglas más valiosas es evitar la duplicidad en el código. Una y solo una vez es la frase de la Programación Extrema. Los autores del libro «The Pragmatic Programmer» (Andrew Hunt y Dave Thomas) utilizan el término «No te repitas tu mismo» (Don’t repeat yourself) también denominado principio DRY.»

No hay más que pasarle el analizador estático de código Sonar a cualquier software para que te empiece a detectar un porcentaje más o menos importante de código duplicado en un programa.

Es complicado evitar la duplicidad de código en equipos de proyecto medianamente grandes o cuando existe el riesgo de no cumplir con los plazos del proyecto (en estos casos se busca que el software funcione, sea como sea y caiga quien caiga).

Sin embargo, sí que estoy de acuerdo en que en la medida de lo posible se debe evitar la utilización de código duplicado o bien si se usa, por el motivo que sea, que posteriormente se refactorice.

Es difícil sacar tiempo para refactorizar, salvo que ya lo tengas integrado dentro de la metodología de desarrollo que se esté utilizando, como por ejemplo si se utiliza Programación Extrema. Sin embargo, si en la planificación de las iteraciones se prevé una dedicación a realizar esta actividad, es posible hacerla independientemente de la metodología de base que se esté utilizando.

Existen generadores de código que pueden producir código duplicado. ¿Es ágil renunciar al generador de código? No lo es, pero hay que tener en cuenta que en el momento en que no se disponga de dicho generador empezarán los problemas, ya que lo que se te soluciona automáticamente, después se tiene que arreglar manualmente y lo mismo el esfuerzo necesario (deuda técnica) reduce considerablemente la mantenibilidad del sistema. Lo que es ágil realmente es retocar el generador para disminuir o eliminar la cantidad de código duplicado que proporciona.

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.