Legado: caracterizar, coser y estrangular
Tres técnicas que permiten tocar con seguridad un sistema que no escribiste, no entiendes, y no puedes parar.
La definición de Michael Feathers tiene cinco palabras: código legado es código sin pruebas.
No es código antiguo. Código de 2011 con una buena suite es más fácil de tocar que lo que escribiste ayer sin ninguna.
Esa definición es buena porque es accionable. "Ese código es malo" es un juicio, genera pelea y no señala nada. "Ese módulo no tiene red de seguridad" es un hecho verificable y dice qué hacer a continuación.
Y porque saca la culpa de la conversación. Quien escribió aquello probablemente estaba bajo un plazo imposible, con la mitad de la información. El problema no es la persona: es la ausencia de retorno rápido cuando alguien cambia una línea.
El círculo vicioso
Sin pruebas no refactorizas con seguridad. Sin refactorizar no consigues probar, porque el código no tiene por dónde entrar.
Tres técnicas rompen ese círculo, y se usan en ese orden.
Técnica 1: prueba de caracterización
El truco es precioso de lo simple que es.
1. Escribe una prueba esperando cualquier valor. Adivina. assert resultado == 0
2. Falla. Y el mensaje de fallo te dice el comportamiento real de hoy: "esperado 0, recibido 1847.32".
3. Congela ese valor en la prueba.
Listo. Fijaste el comportamiento sin necesidad de entender la regla.
Fíjate en lo que pasó: la prueba no verifica si el código está bien. Registra lo que el código hace hoy, para que notes si eso cambia mañana. Que es exactamente lo que necesitas antes de refactorizar.
¿Y si el comportamiento actual está mal? Entonces la prueba documenta el bug, y la corrección pasa a ser una decisión explícita, con la prueba actualizada a propósito, en vez de un cambio accidental que nadie notó.
Un cuidado: deja claro en el nombre que esa prueba describe el legado, no lo deseado.
caracteriza_calculoDeInteresesActual en vez de debeCalcularInteresesCorrectamente.
El golden master
La versión industrial de la misma idea.
Coge mil entradas reales, anonimizadas. Pásalas por el sistema actual. Guarda todas las salidas en un archivo. Versiónalo.
En una tarde, un módulo sin ninguna prueba gana una red de seguridad densa, sin que nadie necesite leer la regla de negocio.
Después de refactorizar, cada diferencia que aparezca es una pregunta: "¿lo cambié a propósito, o lo rompí?"
Esa pregunta hecha durante la refactorización, y no seis meses después en un ticket de soporte, es lo que separa modernizar un sistema legado con método de tocarlo cruzando los dedos.
Los dos cuidados que evitan frustración:
Normaliza antes de congelar. La fecha de hoy, un id aleatorio, el orden inestable de una colección y una dirección de memoria van a hacer que la prueba falle por el motivo equivocado. Sustitúyelos por valores fijos.
Prefiere recortes con significado a un volcado de mil líneas. Un archivo gigante que nadie sabe leer cuando se rompe es casi tan malo como no tener prueba.
En mi experiencia, es la técnica de mayor apalancamiento que existe en código legado: poquísimo código escrito, enorme aumento de seguridad.
Técnica 2: costuras
Una costura (seam, en el original) es un punto donde consigues cambiar el comportamiento sin editar en ese lugar.
El código legado normalmente no tiene ninguna. Crea la conexión a la base, lee el reloj e instancia el cliente HTTP dentro del propio método. Para probarlo, tendrías que levantar medio sistema.
Los tipos de costura:
Por parámetro. El reloj, el cliente HTTP y el generador aleatorio entran por argumento en vez de crearse ahí dentro.
Por subclase. Extrae el tramo difícil a un método protegido y sobrescríbelo en la prueba. Es feo y funciona como paso intermedio.
Por enlace. Cambiar la implementación en la configuración, en el build o en el inyector.
Cómo abrir la primera costura con seguridad: usa solo refactorizaciones comprobadamente seguras (extraer método, renombrar, mover parámetro) y preferiblemente con el refactorizador automático del editor, que se equivoca menos que la mano y no se salta ninguna llamada.
Solo después de abrir la costura escribes la prueba de verdad.
Y la primera costura que yo abriría en cualquier sistema: el reloj.
Busca toda llamada directa a "ahora" y cámbiala por una dependencia inyectada, con el reloj real por defecto. No se rompe nada, y de repente se pueden probar vencimientos, expiraciones, intereses y cambios de mes sin esperar al calendario, y desaparece buena parte de las pruebas inestables.
Suele caber en un PR pequeño, y es la refactorización con mejor relación entre esfuerzo y ganancia en una base legada.
Las señales de que no hay costura, para que las reconozcas en revisión: un new de cliente HTTP
dentro del método; llamada estática al reloj, al entorno o a un singleton; método de ochocientas líneas
que solo recibe un id; herencia profunda en la que el constructor de la clase madre ya abre conexión.
Técnica 3: Strangler Fig
Para sustituir un sistema entero sin parar nada. El nombre viene de la higuera estranguladora, que crece alrededor del árbol huésped hasta sustituirlo.
1. Pon un interceptor. Todo el tráfico pasa por un punto que controlas: un proxy, un gateway, una capa de enrutamiento.
2. Recrea una porción. Elige la de menor riesgo y reimpleméntala solo a ella.
3. Desvía el tráfico. Esa ruta pasa a ir al nuevo. Las demás siguen en el viejo.
4. Repite. Cuando la última ruta migre, el viejo muere de inanición.
Por qué es mejor que reescribir desde cero:
Entrega valor desde la primera porción, y no dentro de dieciocho meses. Cada paso es reversible: basta apuntar la ruta de vuelta. El riesgo queda repartido. Y sobrevive a un cambio de prioridad: si el proyecto se para a la mitad, lo que ya migró sigue en pie.
La reescritura completa, en cambio, persigue un blanco que se mueve: mientras reescribes, el sistema antiguo sigue ganando funcionalidad. Joel Spolsky lo llamó "el peor error estratégico" que puede cometer una empresa de software, y no es exageración.
Lo que sale mal:
La base compartida. Los dos sistemas escribiendo en la misma tabla. Aquí es donde la mayoría se atasca. Los caminos habituales: el nuevo lee de la base antigua durante un tiempo; el antiguo publica eventos vía CDC que el nuevo consume; o separas por porción de clientes, migrando datos junto con el tráfico.
No llegar nunca al final. Sobran las tres rutas más difíciles, y mantienes dos sistemas para siempre, con el doble de coste de mantenimiento.
Contra eso: publica un número visible ("el 78% del tráfico ya está en el nuevo") en un panel, con fecha de apagado acordada. Sin eso, la migración muere en la porción del 60%, que es donde están las rutas aburridas que nadie quiere coger.
Cómo elegir la primera porción: no empieces por el núcleo, ni por la parte más irrelevante. Elige algo con poca dependencia de datos, tráfico real suficiente para probar el camino, y reversión fácil. La lectura de catálogo y los informes suelen ser buenos candidatos.
El objetivo de la primera porción no es entregar mucho valor: es montar el interceptor, el desvío, la observabilidad y el proceso de reversión. De la segunda en adelante, todo eso ya existe.
- 1CaracterizarCongela el comportamiento de hoy sin entender la regla. El golden master lo hace en masa, en una tarde.
- 2CoserAbre un punto donde se pueda cambiar el comportamiento sin editar ahí. Empieza por el reloj.
- 3EstrangularInterceptor delante, una porción cada vez, con número visible y fecha de apagado.
Cómo vender esto al negocio
La pregunta que siempre llega.
No pidas "un sprint de refactorización". Ese pedido pierde contra cualquier funcionalidad, y con razón.
Mete el coste en la estimación de la tarea que ya estaba priorizada, y habla de plazo de entrega, no de elegancia:
"Tocar aquí hoy lleva tres días porque no hay pruebas. Con dos días de preparación, las próximas cinco tareas en esa área pasan a llevar medio día cada una."
Una refactorización vendida como inversión con retorno calculable se aprueba. Vendida como gusto personal, nunca.
Por dónde empezar mañana
Cruza dos listas que ya tienes: qué archivos cambian con más frecuencia (git log) y cuáles concentran
más bugs.
Lo que aparece en las dos es donde la falta de pruebas duele ahora. Empieza por ahí, con un golden master.
Es lo inverso del impulso natural, que es empezar por el módulo más feo, y el módulo más feo puede llevar tres años parado sin molestar a nadie.
Lee esto después
- IA AplicadaPaso 22IA en producción: RAG, eval y prompt injectionTodo el mundo consigue montar una demo que impresiona. Lo que separa la demo del producto son tres disciplinas, y la mayoría de los equipos no tiene ninguna de las tres.Leer artículo
- IngenieríaPaso 21Datos y analytics: del OLTP al lakehouseDos preguntas aparecen en toda empresa: por qué el informe tumbó producción, y por qué el número de mi panel es distinto del tuyo. Las dos tienen la misma causa raíz.Leer artículo
- IngenieríaPaso 19Pruebas que valen lo que cuestanLa prueba no existe para demostrar que el código está bien. Existe para que puedas cambiarlo mañana sin miedo. Ese cambio de objetivo lo reorganiza todo.Leer artículo