← Ir a portada

Refactoring legacy code

El desarrollo de software es la lucha constante del programador por mantener un diseño simple en su software mientras los requisitos aumentan, cambian o se rectifican.

Una de las técnicas más eficaces para conseguirlo es mantener un ciclo de TDD, que consiste en escribir un test, escribir el código necesario para pasar ese test, y repasar el diseño del código reestructurándolo en caso de ser necesario. A éste último paso se le conoce normalmente como refactorizar.
Se suele hablar de que hay que “refactorizar sin piedad”. Esto es, de que no hay que dejar que la deuda técnica de tu software crezca antes de eliminarla, sino que hay que eliminarla en cuanto se detecte.

La pregunta es: ¿y qué pasa cuando tenemos toneladas de código legacy que necesitamos refactorizar y los refactors no son para nada triviales?

Lo primero: ¿qué entendemos por código legacy? La definición más sencilla es la que más me gusta: código sin test.

Lo segundo: ¿estamos seguros de que necesitamos refactorizar? Para esto hay que hacerse dos preguntas más: ¿es este refactor económicamente viable para el cliente? ¿qué valor le aporta? y ¿nos hemos planteado la posibilidad de reescribir el código desde cero?

Respecto al primer punto, habrá que hacer una primera exploración de la profundidad del refactor para estimar cuantas horas nos puede llevar completarlo y por cuantas personas. Y luego habrá que ver qué recibe el cliente a cambio de ello. También vale plantearse que obtendrá el equipo de desarrollo por realizar ese refactor. Si el equipo está siendo ralentizado por código sucio, quizá limpiarlo pueda hacer que futuros desarrollos cuesten mucho menos tiempo.

Para hacer una exploración del tamaño del refactor, funciona muy bien el crearte una rama en tu control de versiones, y ponerte a usar “extract method” como si no hubiera un mañana sin cubrir previamente de test. Al convertir todo en métodos pequeños, en seguida aflora tanto el Feature Envy como el Primitive Obsession. De ahí pueden salirte muchas clases nuevas, cada una con responsabilidades mucho más acotadas y hacerte una idea de qué hacía ese trozo de software realmente. Puedes descartar todos los cambios y borrar la rama, o reintegrar las nuevas clases creadas con la idea de utilizarlas cuando empieces el refactor de verdad.

Respecto a reescribir el código desde cero, si se trata de un código que siempre tuvo errores y que generaba failure demand me plantearía rehacerlo desde cero. Si se trata de una lógica no muy compleja y fácil de entender, también. El problema es que muchas veces estamos ante código sucio que ha sobrevivido al paso del tiempo porque al fin y al cabo cumplía con su función.

Con lo que llegados a este punto, hemos decidido que el refactor es economicamente viable tanto para el cliente como para la empresa que desarrolla el software y hemos descartado la posibilidad de reescribir el código desde cero.

Lo primero que hay que preguntarse es: ¿cómo vamos a probar el nuevo software? Porque como dice Michael Feathers en Working effectively with legacy code hay dos posibilidades: edit and pray or cover and refactor. Y el “cover” no vale con cubrir con tests unitarios, deberemos cubrir a nivel de integración y, sobre todo, funcional.

Para esto creo que es especialmente útil mantener el código anterior y el actual, y no empezar a refactorizar machacando el código existente. Dos técnicas nos pueden ayudar a esto: Feature toggles y Branch by abstraction.

Una vez que tenemos diseñada nuestra estrategia de probar para asegurarnos que todo va como debe y que el nuevo código, antes de añadir nuevas funcionalidades, va a hacer lo mismo que el viejo código, encontraremos muchos problemas a la hora de poder probar el código. La mayoría de problemas vendrán por no poder liberar al código de dependencias externas y probarlo de manera unitaria.

Una vez más Feathers nos da una serie de técnicas que describo aquí brevemente y que he agrupado libremente (sin seguir el orden del libro):

Por supuesto, lo ideal es leerse el libro de Michael Feathers para entender en profundidad las técnicas para romper dependencias, pero espero que publicar mis experiencias estos últimos meses con el ayude a alguien por ahí.  :-)

Edito el post y añado unas notas que me he encontrado por ahí:

Apuntes relacionados:

2 Respuestas a “Refactoring legacy code”

  1. Carlos Ble dice:

    Gran resumen!
    Yo añadiria tambien “Wrap method”, “sprout method”, “sprout class” y “wrap class”.
    Y luego mis amigos los DTOs, que no se si los cita Michael porque todavia tengo el libro a medias ;-)

  2. [...] es el valor de un refactor de código? ¿Cuál su coste? ¿y del pair programming? Se puede hacer un análisis coste-versus-valor de casi [...]

Deja un comentario