Modernizar PHP legacy sin parar la operación
La reescritura completa casi nunca sobrevive al contacto con un sistema que factura cada día. Cómo mejorar una base legacy por partes, empezando por lo que de verdad duele.
Casi todo el mundo que trabaja en backend acaba heredando un sistema que lleva años funcionando, que nadie diseñó del todo y que sostiene la facturación de la empresa. La reacción habitual —yo la he tenido— es proponer reescribirlo.
Esa propuesta casi nunca sobrevive al primer contacto con la realidad, y con razón: es cara, es larga, y durante todo el proceso no aporta nada nuevo al negocio. Se pide confianza durante meses a cambio de llegar, en el mejor de los casos, al punto donde ya estabas.
La alternativa no es resignarse. Es cambiar la unidad de trabajo.
Medir antes de opinar
En una base legacy siempre hay un archivo que todo el mundo señala. Suele ser el más feo, el que tiene funciones de trescientas líneas y nombres incomprensibles.
Casi nunca es el que hay que tocar primero.
Lo feo y lo lento coinciden mucho menos de lo que parece. Hay código horrible que se ejecuta dos veces al día y código razonablemente limpio que está en el camino crítico de cada petición. Si optimizas por criterio estético acabas invirtiendo semanas en algo que no mueve ninguna métrica.
Antes de tocar nada: medir bajo carga real. No en local, no con datos de prueba. Dónde se va el tiempo cuando el sistema está haciendo lo que hace de verdad.
Arrinconar, no reescribir
La técnica que mejor me ha funcionado no es reescribir bloques, sino rodearlos.
Coges una parte del sistema que te duele, defines una frontera clara delante —una interfaz, un servicio, lo que encaje— y haces que todo el mundo pase por ahí. En ese momento no has mejorado nada por dentro: el código sigue siendo igual de malo. Pero has ganado algo decisivo, que es poder cambiarlo por dentro sin que nadie se entere.
A partir de ahí puedes reescribir la implementación, cambiar la estrategia de consulta o meter una caché, y el resto del sistema no se entera. Y si sale mal, vuelves atrás en un sitio, no en cuarenta.
El código legacy rara vez desaparece de golpe. Se va arrinconando hasta que un día queda tan pequeño y tan aislado que sustituirlo es un cambio menor.
Cubrir con pruebas lo que vas a tocar, no todo
Poner el sistema entero bajo pruebas antes de empezar suena responsable y es, en la práctica, otra forma de la reescritura: un proyecto enorme sin valor visible.
Lo que sí funciona es cubrir la frontera concreta que estás a punto de mover. Antes de tocar un bloque, escribes pruebas que fijen su comportamiento actual —incluyendo las rarezas, que probablemente alguien depende de ellas— y luego lo cambias.
Esas pruebas no son documentación de cómo debería funcionar el sistema. Son un contrato de que no lo has roto. Es un objetivo más modesto y mucho más útil.
Cuidado con las rarezas que parecen bugs
En un sistema con años, muchas cosas raras están ahí por un motivo que ya nadie recuerda. Un
if incomprensible puede ser un caso de un cliente concreto que se quejó hace cuatro años.
Antes de eliminar algo que parece un error, merece la pena preguntar. A veces la respuesta es que efectivamente sobra. Otras veces descubres una regla de negocio que no está escrita en ningún sitio. Borrarla no es limpiar código: es romper un caso de uso silenciosamente.
Lo que se gana
Trabajar así es menos satisfactorio que empezar de cero. No hay un momento de inauguración, no hay una foto de antes y después.
Lo que hay es un sistema que cada mes funciona un poco mejor sin haber dejado de funcionar ningún día. Y, sobre todo, una capacidad que se queda en el equipo: la de mejorar lo que hay sin pedir permiso para parar. En sistemas que sostienen la operación diaria de una empresa, esa habilidad vale bastante más que la de escribir código nuevo y limpio en un proyecto vacío.