Dev from trenches

Estrategia refactoring de un producto software

Introducción

En este artículo describiremos una estrategia para hacer refactoring de un producto software.

En este contexto no se usa este termino con la definición de refactoring que dio Martin Fowler, sino que se amplia permitiendo que las tareas de refactorización modifiquen ligeramente el comportamiento del sistema si fuese necesario.. Martin Fowler definió la refactorización según sigue:

Refactoring is a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.

Es inevitable realizar tareas de refactorización en un producto software cobrando mayor importancia según el producto evoluciona en el tiempo incluyendo más features.
Gracias a la refactorización, la deuda técnica del código se puede mantener a lo largo de la vida del producto y esto repercute en que el coste de evolucionar y de mantener el código sea asumible económicamente por la compañía. No hacer refactorización implica ir perdiendo calidad en el producto.

La refactorización de código no suele ser entendida por el equipo de gestión de la compañia que no está en el día a día del desarrollo de software, por lo que será indispensable argumentar y respaldar con datos la necesidad de llevar a cabo tareas de refactorización. Tampoco suele ser entendida por los clientes, por lo que tendremos que lograr que perciban los beneficios de estas refactorizaciones para su negocio.

Tendremos que mostrar transparencia del coste que supone la refactorización de código, así como del coste que supone no llevarla a cabo. Evitaremos realizar refactorizaciones de código sin implicar al resto de la compañía para contar con su respaldo y con los recursos suficientes.

En este artículo las tareas de refactorización se asocian a partes concretas de un producto software. Por parte del producto nos estamos refiriendo a un componente o módulo del producto, al backend, middleend o frontend o incluso a un subproducto del producto.

Normalmente las tareas de refactorización de código habrá que hacerlas mientras evoluciona el producto y mientras el producto presta el servicio a nuestros clientes. Es por ello que se necesitará trazar un plan para llevar a cabo estas tareas de refactorización identificando los riesgos que puedan darse al refactorizar el código.

Aspectos técnicos de la refactorización

No entraremos en detalle en los aspectos técnicos de refactorización. Sin embargo como parte de la estrategia de refactoring de un producto software es aconsejable tenerlos en cuenta pues influirán en el éxito.

Refactorizar una parte del sistema requiere antes leer con detenimiento el código y entenderlo. Requiere ejecutarlo multitud de veces para estudiar su comportamiento dinámico y ver sus carencias ante diferentes casos de uso. Una vez hecho esto intentaremos simplificar su diseño y cubrir los casos no cubiertos.

Un desarrollador es adecuado para hacer refactorizaciones si se muestra abierto a aceptar otras formas de pensar y que están plasmadas en el código a refactorizar. De lo contrario lo que hará será tirar el código existente y volverlo a hacer. Ese no es el objetivo, pues es muy probable que se pierda lógica necesaria.
Es preciso que el desarrollador sea capaz de simplificar el diseño que se ha ido complicando según ha pasado el tiempo, al haber introducido modificaciones sin adaptar previamente el diseño. Si el desarrollador de por sí realiza diseños nuevos complejos, entonces no será ni mucho menos la persona adecuada para refactorizar.

Si la parte a refactorizar no tiene tests unitarios o tests de integración será necesario que el desarrollador tenga un conocimiento profundo del negocio, del producto y de qué responsabilidades se le han atribuido a esta parte dentro del producto. Una vez establecidas estas responsabilidades, deberá diseñar unos tests unitarios y tests de integración que validen estas responsabilidades. En esta situación es aconsejable que la refactorización no lo haga un único desarrollador sino un grupo de desarrolladores en pair programming.
Después de refactorizar una parte del producto tendremos que probar el resto del producto con esta parte refactorizada y validar el comportamiento del producto.

Visión global de la estrategia

En este punto se describe la visión global de la estrategia de refactoring de un producto software propuesta, definiendo cuáles son los pasos a seguir.

El siguiente diagrama representa visualmente esta estrategia:

Agrandar imagen

Identificación de las partes del producto

Una tarea clave es una estrategia de refactoring de un producto software es identificar que partes componen nuestro producto. La idea es conocer como están distribuidas en el código la implementación de las features del producto.

En este contexto, una parte estará compuesta por uno o varios servicios. Entendemos por servicio software una clase que tiene atributos que modelan conceptos del negocio y que tiene operaciones sobre estos atributos. Quedan excluidas por tanto clases utilidades que no tienen que ver con el negocio o clases que solo modelan datos complejos sin operaciones.

Es preciso que hagamos este ejercicio para que posteriormente podamos medir la calidad de cada parte para saber si es necesario refactorizar esta parte o no.

Esta tarea será más sencilla si nuestro producto tiene una arquitectura basada en microservicios. Si por el contrario tenemos una arquitectura monolítica entonces tendremos que hacer un análisis mayor, pues la implementación de las features estará entrelazada.

Cuando sepamos esta relación entre features y partes del producto entonces podremos saber el impacto que tiene en el código cuando es necesario modificar una feature. Y al contrario, cuando tengamos que refactorizar una parte para incrementar su calidad, sabremos a qué features del producto afectará.

Tendremos features que por su naturaleza funcional esté implementada en muchas partes del producto. Serán features transversales al producto. Y también tendremos features verticales que estén en pocas partes.

En un artículo futuro profundizaremos en cómo se puede realizar está identificación de las partes de un producto. Por ahora, lo que necesitamos saber es que una de las salidas de esta tarea de identificación de partes es el catálogo de features y partes donde se puede ver qué partes del producto implementan cada feature.

Indicadores de calidad

En la estrategia de refactoring de un producto software tendremos que considerar indicadores que nos informen sobre la calidad del producto. Estos indicadores se calcularán para cada parte del producto para que luego podamos decidir qué partes vamos a refactorizar.

Los indicadores que se proponen aquí podemos llamarlos indicadores de calidad de caja negra, haciendo una comparativa con los tests de caja negra. Es decir, no evalúan cómo está desarrollado por dentro el producto sino que se basan en datos económicos.

Si quisiéramos utilizar otros indicadores que tuvieran en cuenta el diseño del producto, tendríamos que buscar una forma de relacionar la calidad de este diseño con el aspecto económico. Esto es así porque necesitaremos argumentar a la compañia de la idoneidad de realizar estas tareas de refactorización, sin hacer alusión a cuestiones técnicas.

Podemos diferenciar dos indicadores. Un indicador que nos informe sobre el coste que nos supone el mantenimiento del producto y otro indicador que nos informe sobre el coste de su evolución.

Cabe mencionar que utilizar herramientas para análisis de código estático como SonarQube es muy acertado y ayuda a garantizar la calidad del software que se desarrolla. Pero justificar a la compañia una refactorización de código con el coste que esto supone a partir de este análisis no es fácil. Además, los análisis aportados por este tipo de herramientas no siempre están alineados con los indicadores de calidad del negocio.

Necesitamos definir unas medidas que nos cuantifiquen cada indicador.

Estas medidas serán tomadas para un periodo de tiempo a definir para este análisis. Será necesario definir un modo de combinar las medidas que cuantifican al indicador para obtener un único dato. Un modo sería usando una fórmula matemática ponderada.

Para calcular estas medidas necesitaremos datos de histórico y para ello habremos llevado previamente un registro de los costes imputados a tareas de desarrollo de cada parte del producto identificadas previamente. Sin estos datos no será posible cuantificar objetivamente los indicadores de calidad que se proponen.

Medidas del indicador de evolución

Para cuantificar este indicador podemos tomar varias medidas:

Medidas del indicador de mantenimiento

Podemos tener varias medidas para cuantificar este indicador:

En cuanto a la resolución de un bug hay que considerar que no solo conlleva la corrección en sí misma sino también todas las tareas asociadas a tener varias ramas de desarrollo en el repositorio de código. Por ejemplo, tendremos casos que la resolución de un bug suponga las siguientes tareas:

  1. Abrir la rama de hotfix (4.1.2) y mezclarla con la rama master al finalizar.
  2. Bajar la modificación a la rama develop donde ya se está desarrollando la versión 4.3.
  3. Bajar la modificación a la rama release donde se está estabilizando la versión 4.2.
  4. Bajar la modificación a todas las ramas de feature abiertas de la versión 4.3.

Hay que considerar que la resolución del bug en la 4.1.2 no tiene porque ser la misma que la resolución en las ramas 4.2, 4.3 y ramas de feature porque la parte del producto afectada haya variado y por lo tanto el coste sea aún mayor.

Inventario de partes del producto con indicadores

Una vez que tenemos definida la forma de cuantificar los indicadores de calidad gracias a las medidas, pasaremos a realizar un inventario de estos indicadores para cada parte del producto.

Un ejemplo de inventario que podemos tener es el que se muestra a continuación. El rango cuantitativo de cada indicador es de 1 a 5 donde el coste crece en orden ascendente (5 representa el mayor coste).

 

Parte del producto Indicador de mantenimiento Indicador de evolución
Backend de configuración del producto 3 4
Frontend de configuración del producto 4 5
Backend de gestión de usuarios 1 1
Frontend de gestión de usuarios 1 2
Backend de gestión de flotas de vehículos 2 3
Frontend de gestión de flotas de vehículos 3 4

Una conclusión que sacamos viendo este inventario es que el valor del indicador de evolución siempre es mayor que el valor del indicador de mantenimiento. Si ya es costoso mantener el código, más costoso será aún hacerlo evolucionar.

Este inventario nos da evidencias de cómo de bien o mal está cada parte del producto. Pero teniendo en cuenta solamente estos datos no decidiremos qué partes vamos a refactorizar y en qué orden. Para decidir esto necesitamos analizar varios factores.

Factores de decisión

Dentro de la estrategia de refactoring de un producto software podemos identificar factores que nos ayudarán a tomar la decisión de qué partes refactorizar y qué orden es el más oportuno seguir. Cada compañía identificará sus propios factores de decisión.

Además, deberemos asignar un peso a cada factor para que luego podamos obtener un orden en las partes a refactorizar. Estos pesos podrán variar según pasa el tiempo y los intereses de la compañia cambien, por lo que habrá que volver a evaluar las partes a refactorizar.

Roadmap del producto

El roadmap del producto contendrá las próximas features a modificar o incluir en el producto. Es posible que alguna de estas features afecte a una o varias partes a refactorizar.

El roadmap también nos dará pistas de qué evolución hay prevista para cada parte del producto. Si alguna parte a refactorizar no va ser evolucionada en un futuro próximo o va a ser deprecada por no ser usada, entonces quizá no valga la pena refactorizar esta parte, a no ser que esta parte tenga un indicador de mantenimiento pésimo, en cuyo caso sí que tenga que ser refactorizada.

Estrategia de deslocalización

La compañia puede tener definida una estrategia de deslocalización para el producto. Dependiendo de la distribución del trabajo definida en la estrategia, cobrará mayor o menor importancia mejorar la calidad de una parte del producto para garantizar el éxito en la deslocalización.

Condiciones del entorno

Las condiciones del entorno que rodean a nuestro producto influyen en las tareas de refactorización.

La infraestructura sobre la que ejecuta nuestro producto puede sufrir cambios y nuestro producto necesitará adaptarse a esta nueva infraestructura. Es posible que el producto tenga que refactorizarse para ejecutar sobre una plataforma de microservicios y actualmente sea un monolito.

En estos últimos años los perfiles profesionales de desarrolladores tienden a especializarse cada vez más en nuevos lenguajes y frameworks. Si nuestro producto no renueva la tecnología en la que está desarrollado será difícil encontrar nuevos profesionales.

Habrá muchas otras condiciones como las anteriores que nos implique una refactorización.

Partes del producto a refactorizar

Una vez que tenemos el inventario con las partes del producto incluyendo los indicadores de calidad y tenemos los factores de decisión identificados, lo siguiente será obtener a partir de esta información una lista con las partes del producto a refactorizar en un orden concreto. Es posible que nos encontremos con varias partes a refactorizar con la misma urgencia, por lo que habrá que aplicar criterios adicionales para decidir una.

Hasta aquí lo que hemos obtenido es qué trabajo de refactorización hay que llevar a cabo. Ahora lo siguiente que tenemos que hacer es diseñar un plan para llevar a cabo en el tiempo todas esta tareas de refactorización.

Plan de refactorización

Una vez que tenemos identificadas las partes del producto a refactorizar y que orden es el más conveniente a seguir, debemos definir un plan para implementar la estrategia de refactoring de un producto software.

Cabe resaltar que no existirá un único plan de refactorización sino que habrá un plan por cada horizonte de tiempo que se plantee.

Axiomas del plan

Las tareas de refactorización se harán al mismo tiempo que se implementan nuevas features.

Es preciso reservar tiempo y presupuesto para las tareas de refactorización.

Será indispensable implicar a la compañia en estas tareas de refactorización debiéndose previamente obtener su aprobación. El código es un activo de la compañia y por lo tanto es responsabilidad de todos asegurar su calidad. Seguramente sea necesario justificar estas tareas de refactorización y lo podremos argumentar mostrando el inventario de las partes del producto con los indicadores de calidad que previamente hayamos realizado.

Un objetivo del plan será minimizar la posibilidad de desestabilizar el producto, repartiendo las tareas de refactorización entre varias versiones.

Serán ineludibles desarrollar adaptaciones temporales entre modelos e interfaces nuevos surgidos tras la refactorización de varias partes y los modelos e intefaces antiguos de varias partes aún no refactorizadas.

El coste de realizar estas adaptaciones temporales deberá ser analizado e incluido en el coste de refactorización. Si este coste es muy elevado es posible que decidamos o no refactorizar ninguna parte o refactorizar todas las partes relacionadas a la vez, para evitar estas adaptaciones temporales. Por otro lado habrá que tener en cuenta el riesgo que esto conlleve.

Pondremos especial cuidado en que la estimación del coste de tareas de refactorización se haga por lo alto porque en estas tareas suele haber desvíos entre lo estimado y lo que realmente luego cuesta, debido a la mala calidad del código a modificar.

Para el cálculo de este coste obviamente habrá que incluir la estimación de coste de adaptar (y mejorar si fuera el caso) las pruebas existentes: pruebas unitarias, pruebas de integración, pruebas de ensamblaje y pruebas de aceptación.

El plan debe tener en cuenta el valor percibido por los clientes de cada versión del producto. Si una versión nueva del producto solamente contiene refactorizaciones para hacer menos costosa la evolución del producto, será difícil de vender al cliente. Mucho más si la puesta en producción de esta nueva versión le supone un coste, ya sea económico, de imagen o de otro tipo. Tendremos que ir combinando refactorizaciones que aporten valor a la compañía y features y refactorizaciones que aporten valor a los cliente.

Secuenciación

Consideraremos las siguientes entradas de información al plan:

  1. Partes del producto a refactorizar con el orden más conveniente.
  2. Roadmap del producto con las siguientes features a modificar o incluir.
  3. Presupuesto disponible para cada feature y para tareas de refactorización.

Tendremos que ver para cada feature a modificar, qué partes del producto de las que vamos a refactorizar se ven afectadas por la modificación de esta feature. Esto lo podremos obtener del catálogo de relaciones entre features y partes que mencionamos antes.

Lo siguiente a definir es decidir qué features se incluirán en cada una de las versiones, dentro del horizonte de tiempo considerado por el plan. Para ello consultaremos el roadmap del producto.

Se favorecerá que si una feature afecta a una parte a refactorizar, se incluya la refactorización de esta parte en la misma versión. Este criterio puede modificar el orden que obtuvimos anteriormente de las partes a refactorizar.

Veamos un ejemplo:

Agrandar imagen

En principio tenemos el siguiente orden de partes a refactorizar: P2, P0, P1.

El roadmap contiene las siguientes features a incluir en versiones: F0, F1, F2, F3, F4.

Las versiones incluirán lo siguiente:

Como vemos, el orden original de las partes a refactorizar ha variado. En este caso las necesidades del cliente ha favorecido otro orden diferente.

Una vez que ya tenemos definido el alcance de las próximas versiones, tendremos que validar que el coste de refactorización acumulado de las versiones encaja en el presupuesto total reservado para la refactorización. Si el coste fuera mayor, entonces habrá que reducir el alcance de cada refactorización.

Retrospectiva

Registraremos las tareas de refactorización realizadas en cada parte del producto en las sucesivas versiones para posteriormente poder cuantificar que valor hemos ganado con estas refactorizaciones.

Nuevamente utilizaremos dos vertientes de datos: datos para cuantificar el indicador de mantenimiento y datos para cuantificar el indicador de evolución.

A medida que estas versiones incluyendo refactorizaciones entren en producción volveremos a registrar los bugs reportados y con estos datos volveremos a sacar indicadores de calidad de mantenimiento de cada parte.

Según vayamos incluyendo nuevas features en sucesivas versiones podremos calcular los nuevos indicadores de calidad de evolución de cada parte.

Con todo esto podremos ver la tendencia en el tiempo de estos indicadores y comprobar que han mejorado.

Conclusiones

Contar con una estrategia de refactoring de un producto software dentro de la compañia es clave para mantener un buen diseño en el código de nuestro producto y poder decrecer la deuda técnica acumulada.

No obstante aún siendo clave, es una labor que es difícil justificar a resto de la compañia que no está en el día a día del desarrollo de software. Es por esto que tendremos que respaldarlo con indicadores de calidad basados en datos económicos.

El orden más conveniente para aplicar estas refactorizaciones será determinado por los factores de decisión que defina la compañia.

Las tareas de refactorización habrá que realizarlas en muchos casos sobre un producto que está prestando servicio a los clientes y sobre el que además iremos añadiendo nuevas features. Hay que diseñar un plan para secuenciar en el tiempo estas tareas de refactorización.

Por último registraremos estas tareas de refactorización para poder calcular el valor ganado que nos han aportado.



Deja una respuesta

Subscríbete::About