Señales de que tu software se está deteriorando

Si en vez de leer prefieres escuchar, dale al play.

Señales de que tu software se está deteriorando
17:30

Cuando comencé mi carrera como desarrollador de software, uno de los muchos libros que leí fue "Growing Object Oriented Software Guided By Tests" de Steve Freeman y Nat Pryce. Una de las ideas que más me impactó fue la noción de que el software debe ser cultivado y cuidado con el tiempo. Así como cuidamos nuestras plantas y flores para asegurarnos de que crezcan de la mejor manera posible, debemos cuidar continuamente nuestro software. En este blog, me gustaría explorar por qué esto es importante, destacar algunas señales que indican que tu software está en declive y algunas de las cosas que puedes hacer para abordarlo.

¿Qué es el deterioro de Software?

El deterioro del software ocurre cuando una base de código se vuelve ingobernable debido a la complejidad no gestionada y un diseño inadecuado. A medida que se realizan cambios de código "fáciles" en lugar de abordar las necesidades de diseño subyacentes, la complejidad aumenta y los componentes se acoplan inapropiadamente, dificultando cada vez más los cambios.

Además, el entorno tecnológico cambia constantemente, como lo demuestra la proliferación y la rápida obsolescencia de frameworks de JavaScript. Si no actualizamos nuestro software para adaptarse a estas nuevas tecnologías, nos arriesgamos a usar frameworks sin soporte activo, lo que deja el software vulnerable a errores y riesgos de seguridad. Mantener el software estático en un entorno dinámico contribuye al deterioro.

Indicadores de deterioro del software

Ahora que tenemos una comprensión común del deterioro del software, me gustaría destacar algunas señales que podrían indicar que está comenzando a aparecer. Es importante tener en cuenta que esta sección se refiere solo a indicadores. Es decir, estas señales te dirán que tienes un problema, pero no necesariamente te dirán la causa subyacente del problema.

Fragilidad, tiempo de entrega, demandas del negocio, fallos en cambios, métricas en declive, cobertura de pruebas, deuda técnica, DevEx

1. Fragilidad

La fragilidad se refiere a un software que tiende a fallar en muchos lugares cada vez que se realiza un cambio, a menudo incluso en áreas que no están conceptualmente relacionadas con el cambio que se está realizando. A medida que esto aumenta, el software se vuelve muy difícil de mantener porque cada nuevo cambio introduce numerosos defectos nuevos. En el mejor de los casos, estos defectos se detectan temprano mediante una suite de pruebas automatizadas. En el peor de los casos, se encuentran en producción por los usuarios finales. Esta fragilidad puede llevar a una pérdida de credibilidad para el software y el equipo que lo desarrolla.

2. Aumento del tiempo para entregar funcionalidades

Un indicador claro de deterioro del software es el incremento en el tiempo requerido para agregar nuevas funcionalidades. Este aumento continuo señala rigidez en el código y puede indicar problemas subyacentes en la estructura del software. Esto significa efectivamente que el código es difícil de cambiar, a menudo porque está estrechamente acoplado. Por ejemplo, un nuevo cambio causa una cascada de cambios subsiguientes en módulos dependientes dentro de la base de código. Esto resulta en equipos que a menudo temen abordar problemas no críticos porque no saben el impacto completo de hacer un cambio o cuánto tiempo tomará ese cambio.

3. Dificultad para mantener el ritmo con las demandas del negocio

Estrechamente relacionado con el punto anterior, cuando un equipo comienza a tener dificultades para mantener el ritmo con las demandas de la organización, también es una indicación de que la base de código podría estar en un estado poco saludable. Por ejemplo, tal vez el modelo de dominio en la base de código ya no es adecuado y requiere un nuevo diseño para facilitar las necesidades de los requisitos comerciales. Cuando un equipo de software no puede mantener el ritmo con las demandas de la organización, hay un problema serio. Significa que la organización corre el riesgo de perder cualquier ventaja competitiva que tenga en el mercado ya que toma más tiempo enviar nuevas funcionalidades a sus clientes.

4. Tasa de fallos en los cambios

No sirve de nada desplegar tu software en producción con frecuencia si estos despliegues resultan en cambios que afectan la capacidad de tus clientes para usar tu producto. Queremos desplegar en producción regularmente, pero no a costa de la calidad. La tasa de fallos en los cambios es una métrica que fue introducida por el equipo de Investigación y Evaluación de DevOps (DORA) y es una medida de cuántas veces un despliegue a producción resulta en un fallo de algún tipo. Una alta o creciente tasa de fallos en los cambios sugiere que nuestro software no está tan saludable como debería y probablemente está perdiendo controles de calidad adecuados en el camino hacia la producción.

5. Métricas de software en declive

Las métricas de software son esencialmente medidas de ciertos atributos de calidad de una base de código. Es importante señalar que desaconsejaría buscar números absolutos en cualquiera de estos atributos. En cambio, es mucho más útil centrarse en la tendencia a lo largo del tiempo. Un declive nos dice que nuestro software se ha vuelto menos saludable en un área determinada y debemos tomar medidas para resolverlo, para evitar que se establezca el deterioro del software.

Hay una serie de métricas que se pueden recopilar sobre una base de código de software, y la siguiente lista no es exhaustiva, pero es una colección de las que personalmente he encontrado útiles en los equipos con los que he trabajado: complejidad ciclomática, acoplamiento y cobertura de pruebas.

  • Complejidad ciclomática

A menudo escucho a equipos que utilizan el número de líneas de código (LOC) en un método o archivo determinado como una medida de complejidad. Esto por sí solo no es una métrica suficientemente buena. Por ejemplo, un pequeño programa de software que totaliza 50 líneas de código no suena demasiado complejo. Pero si esas 50 líneas de código contienen 25 líneas de constructos de código consecutivos "if-then", entonces el código es realmente extremadamente complejo. Aquí es donde la complejidad ciclomática es una métrica mucho mejor. Es una medida del número de diferentes caminos o ramas. Basado en la teoría de grafos y desarrollado por Thomas J. McCabe, proporciona una medida cuantitativa del número de caminos linealmente independientes a través del código fuente.

  • Acoplamiento

Hay dos categorías de acoplamiento de componentes de software. Primero, acoplamiento aferente. Esto se refiere al número de clases en otros paquetes que dependen de clases dentro de un paquete particular. Es un indicador de la responsabilidad del paquete. La otra categoría es el acoplamiento eferente. Este es el número de clases en otros paquetes de las que dependen las clases en el paquete. Este es un indicador de la dependencia del paquete en externalidades. Cuanto mayores sean los niveles de acoplamiento en ambas categorías, más difícil será cambiar el código del software. Queremos esforzarnos por componentes acoplados de manera flexible, ya que aumenta la flexibilidad, la usabilidad y reduce la superficie de cambio. En contraste, las bases de código con componentes estrechamente acoplados tienden a ser más frágiles y mucho más difíciles de cambiar y evolucionar con el tiempo.

6. Cobertura de pruebas

La cobertura de pruebas es una medida de la cantidad de código de producción en una base de código de software que tiene una prueba automatizada para ejercitar y verificar su comportamiento. Esta métrica generalmente se expresa como un porcentaje, es decir, un equipo puede decir "tenemos un 70% de cobertura de pruebas". Como mencioné anteriormente en el blog, siempre desaconsejaría buscar un número absoluto con métricas de software y esto es especialmente cierto para la cobertura de pruebas. En cambio, favorezca observar las tendencias a lo largo del tiempo y las medidas de cobertura de pruebas relativas entre diferentes partes de una base de código, particularmente aquellas que han cambiado recientemente. Por ejemplo, si tu equipo ha realizado un alto número de cambios recientemente en un área de la base de código para soportar una nueva funcionalidad, sería alarmante si la cobertura de pruebas en esa área hubiera disminuido.

7. Aumento de la deuda técnica

La deuda técnica es una metáfora que fue concebida por Ward Cunningham en 1992. Él escribió:

"Enviar código por primera vez es como endeudarse. Una pequeña deuda acelera el desarrollo siempre que se pague puntualmente con una reescritura... El peligro ocurre cuando la deuda no se paga. Cada minuto gastado en código no del todo correcto cuenta como interés sobre esa deuda. Organizaciones enteras de ingeniería pueden estar dedicadas a pagar intereses de deuda técnica."

En otras palabras, la deuda técnica es cuando priorizamos la entrega rápida en lugar de la calidad del código. En el corto plazo, esto es aceptable. Sin embargo, si la deuda técnica no se paga a tiempo, puede conducir al deterioro del software.

8. Developer experience

Mejorar la Developer Experience (DevEx) puede ser un indicador crucial del deterioro del software. Los equipos de desarrollo pasan la mayor parte de su tiempo trabajando dentro de una base de código. Si esa base de código es difícil de manejar, afectará su moral, lo que a su vez puede llevar a una disminución en la moral del equipo en general. Prestar atención a la DevEx puede revelar mucho sobre el estado de las bases de código en las que trabajan. Una base de código que los desarrolladores disfrutan trabajar generalmente es un signo de una base de código saludable. Después de todo, ningún equipo disfruta trabajar con una base de código compleja, altamente acoplada y frágil.

Soluciones para el deterioro del software

Ahora que hemos identificado algunas señales que indican que tu software se está deteriorando, hablemos de algunas soluciones que puedes implementar para abordar este problema:

1. Valorar la testabilidad

La testabilidad es fundamentalmente una medida de la facilidad con la que se puede probar un software específico. También puede entenderse como el grado de probabilidad de que una prueba identifique un defecto en el software. La teoría sostiene que si el software es difícil de probar, es más probable que las pruebas no sean completas. Las pruebas son vitales, especialmente las pruebas automatizadas. El libro "Growing Object Oriented Software Guided By Tests" destaca que las pruebas automatizadas no solo se centran en verificar que el software funcione como se espera, sino también en diseñar el software mismo.

Por esta razón, el Desarrollo Guiado por Pruebas (TDD) es una parte crucial del proceso de desarrollo de software. Al valorar la testabilidad y darle prioridad, permitimos que el diseño de nuestro software evolucione gradualmente de manera incremental. Cada iteración se construye sobre la anterior, desarrollando una suite de pruebas automatizadas que actúa como una red de seguridad cuando se necesitan realizar cambios

Test Pyramid: UI, service, unit

2. Buscar la simplicidad

Un principio clave en el diseño de software es la simplicidad. La comunidad de Extreme Programming (XP) habla de "Diseño Simple" y frases como "hacer lo más simple que podría funcionar" y "no lo necesitarás" (comúnmente conocida como YAGNI). Al buscar la simplicidad, logramos dos cosas: permitimos que nuestro software evolucione incrementalmente en una serie de pequeños pasos que se construyen uno sobre otro, y reducimos la complejidad. Cuanto más complejo sea el diseño, más difícil será razonar sobre él. También es posible que diseñemos pensando en el futuro y nuestras predicciones sean incorrectas. Como resultado, el diseño complejo que construimos puede no ser adecuado y requerir re-trabajo, cuando hubiéramos sido mucho mejores manteniéndolo simple desde el principio.

3. Apoyar el cambio incremental

El cambio incremental implica que los cambios que deben realizarse en una base de código de software se descomponen en pequeños fragmentos y se aplican de manera secuencial. Esto reduce el alcance de cada cambio y, por lo tanto, la complejidad y el riesgo asociado con ellos. Queremos que nuestras bases de código de software puedan soportar este enfoque de cambio incremental.

4. Establecer directrices de codificación comunes

Es vital que un equipo de desarrollo de software comparta una visión común de lo que constituye un software "saludable". Esto garantiza que todos los miembros trabajen hacia objetivos compartidos y establezcan un compromiso colectivo para mantener altos estándares de calidad en el código y el software. Adoptar un conjunto de directrices y principios colaborativos es fundamental para lograr esto. Estos principios pueden formalizarse a través de reglas automatizadas de linting, métricas de código y otros estándares relevantes. Una base de código cohesiva y consistente facilita la colaboración, mientras que las diferencias en estilos de diseño y código pueden complicar su mantenimiento y evolución con el tiempo.

5. Refactorizar, pequeño y frecuentemente

En el libro "Extreme Programming Explained", Kent Beck compara el desarrollo ágil de software con conducir un automóvil:

"Usamos la conducción como una metáfora para desarrollar software. Conducir no se trata de apuntar el automóvil en una dirección y mantenerlo así; conducir se trata de hacer muchas correcciones de curso pequeñas".

Si bien la metáfora se utilizó principalmente en el libro para hablar sobre la planificación en proyectos ágiles de desarrollo de software, creo que también se aplica al diseño de software. Veo el acto de refactorizar como las correcciones de curso a las que se refiere Kent Beck y al hacer esto frecuentemente, podemos mantener cada refactorización pequeña para hacer muchas correcciones de curso pequeñas. Cada una de estas refactorizaciones, aunque no cambia la funcionalidad del software, desempeña un papel vital. Refina el software un poco cada vez para adaptarlo a un nuevo entendimiento que tenemos sobre el mundo y mantener el código manejable.

Si nos encontramos en una posición donde tenemos que abordar refactorizaciones a gran escala, generalmente es señal de que no hemos estado haciendo refactorización frecuente y pequeña. Como resultado, nuestro diseño de software se ha desviado del modelo de dominio del mundo real y ahora ha surgido un nuevo requisito que ya no encaja en el modelo que tenemos en nuestro software. Alternativamente, las refactorizaciones a gran escala pueden ser señal de alta complejidad en una área de la base de código que se ha desarrollado con el tiempo y ha llegado a un punto en el que el equipo ya no puede agregar cambios de manera efectiva en esa área de la base de código.

6. Radiadores de información

En el desarrollo de software, los radiadores de información son visualizaciones que muestran el estado actual del software, como el estado de la compilación, la suite de pruebas automatizadas y métricas del software. Estos se colocan en monitores grandes dentro del espacio del equipo, siendo visibles no solo para el equipo de desarrollo, sino también para las partes interesadas dentro de la organización. Utilizar radiadores de información permite a los equipos mostrar abiertamente la salud de su software, amplificando cualquier problema potencial. Por ejemplo, si las pruebas automatizadas fallan, esto es visible de manera prominente, promoviendo una acción inmediata. Adoptar radiadores de información fomenta una cultura de transparencia, demostrando que el equipo no tiene secretos para los visitantes ni para ellos mismos, abordando los problemas de frente en lugar de ignorarlos.

Gráfico de deuda técnica

Un ejemplo común es el "Gráfico de Deuda Técnica", colocado en el espacio del equipo, donde se registran los elementos de deuda técnica según su esfuerzo e impacto. Este gráfico proporciona una rápida visualización de la cantidad y tipo de deuda técnica del equipo, facilitando discusiones regulares para gestionar y priorizar estos elementos.

Conclusiones

El deterioro del software es un problema común que puede afectar a cualquier equipo de desarrollo si no se maneja adecuadamente. Al estar atentos a las señales de deterioro y tomar medidas proactivas para abordarlas, podemos mantener nuestras bases de código saludables y fáciles de mantener a largo plazo. La refactorización regular, las pruebas automatizadas, el monitoreo y la capacitación continua son solo algunas de las herramientas que podemos utilizar para prevenir el deterioro del software y asegurar que nuestro software continúe proporcionando valor a nuestros usuarios.

Si no estás seguro de cómo lograrlo, conoce sobre nuestra Evaluación de calidad de software y sistemas

Evalúa la calidad de tu software con nuestro Software Quality Assessment