Saltar al contenido principal
Engineering craft beyond tooling — design patterns, refactoring, code review, advanced testing strategy, and reading code you did not write.

Software Engineering Craft

Engineering craft beyond tooling — design patterns, refactoring, code review, advanced testing strategy, and reading code you did not write.

Refactorización de código legacy

El código legacy es el código que temes — código sin tests, con intención poco clara y una larga sombra de “nadie sabe bien qué depende de qué”. Reescribir desde cero es el instinto de novato y el último recurso del senior. Refactorizar in-place es el verdadero oficio: cambiar el sistema pieza a pieza, cada paso reversible, nunca varado en un estado a medio construir.

Qué hace difícil al legacy

Tres propiedades se componen:

  1. Sin tests — no hay red de seguridad, así que cualquier cambio es una apuesta.
  2. Sin un solo dueño — quienes lo escribieron se fueron; los dueños actuales saben menos que tú.
  3. Acoplamiento oculto — renombrar una función rompe un script en otro repo que nadie recordaba.

Un rewrite greenfield hereda los tres problemas, y reintroduce cada bug que el código legacy acumuló. El refactor in-place preserva el historial de bugs de producción mientras mejora la estructura.

El Strangler Fig

Planta un sistema nuevo junto al viejo. Dirige una porción de tráfico hacia él. Cuando esa porción funciona, dirige más. Cuando el sistema nuevo maneja todo, jubila al viejo.

llamadores → [ router ]
               ↓        ↓
          sistema viejo   sistema nuevo
               ↑                ↑
        superficie completa hoy    slice nuevo hoy, todo mañana

Propiedades clave:

  • Reversible en cada paso: rollback del tráfico si el nuevo slice rompe.
  • Paralelo: el sistema nuevo se puede construir mientras el viejo sigue generando ingresos.
  • Sin fecha límite big-bang: la única fecha que importa es “viejo jubilado”, y solo te comprometes cuando el nuevo maneja demostrablemente toda la superficie.

El patrón funciona a múltiples escalas: un microservicio reemplazando un módulo, una base de datos migrando por tenant a un esquema nuevo, una transición de framework de UI detrás de un feature flag.

Seams

Un seam es un lugar en código existente donde puedes alterar el comportamiento sin editar el código circundante — usualmente sustituyendo una dependencia. Ejemplos:

Tipo de seamEjemploCuándo exponerlo
Object seamInyecta un fake en la construcciónLa clase es instanciable en tests
Link seamCambia un import de módulo por un stub en tiempo de testControlas el build / module map
Pre-processor seam#ifdef TEST elimina APIs no disponiblesEl lenguaje lo soporta; raro en stacks modernos

La mayoría del código legacy no tiene seams. El primer refactor es introducir uno: extraer la dependencia detrás de una interfaz, luego el cambio real ocurre contra la interfaz. Extract Interface es el movimiento habilitante que desbloquea todo lo demás.

Branch-by-Abstraction

Necesitas refactorizar LegacyPaymentService a NewPaymentService sin congelar el desarrollo. El patrón:

  1. Crea una abstracción (PaymentGateway) que el servicio viejo implemente.
  2. Actualiza cada llamador para depender de la abstracción, no del servicio concreto.
  3. Implementa NewPaymentGateway contra la misma abstracción — ejecuta en paralelo.
  4. Gira el routing, gateado por feature flag por tenant o por región.
  5. Jubila LegacyPaymentGateway.

El equipo sigue enviando cambios de producto durante el refactor — mantienen LegacyPaymentGateway actualizado mientras el nuevo se construye. Los refactors big-bang fallan precisamente porque pausan el desarrollo de producto; branch-by-abstraction nunca lo pausa.

El Método Mikado

Para refactors invasivos donde cada cambio parece engendrar dos prerequisitos, el método Mikado funciona como un árbol recursivo de undo:

  1. Enuncia el resultado deseado (“extraer parseConfig a su propio archivo”).
  2. Prueba el cambio. Si el build / test falla con una dependencia, tienes un nuevo sub-objetivo (“necesito exponer el tipo Config”). Revierte.
  3. Recurre sobre cada sub-objetivo hasta que uno funcione limpio.
  4. Aplica en orden inverso al árbol, recommiteando cada capa.

El método convierte “rompí todo” en “tengo una lista de cambios alcanzables”. El repositorio siempre compila antes de cada commit; nunca varas al equipo en un estado roto.

Continuo vs Big-Bang

PropiedadBig-bangContinuo
VisibilidadInvisible hasta “terminar”Cada paso va a producción
RiesgoConcentrado en una fecha límiteDistribuido en muchas apuestas pequeñas
ReversibilidadA menudo imposible una vez empezadoCada paso es revertible
Costo culturalEl autor puede ser héroe o chivo expiatorioRefactorizar es un martes normal

Elige siempre continuo, a menos que el producto requiera una migración de un solo corte (raro; “relanzamos en la Super Bowl”). Incluso entonces, prepara strangler por debajo — el momento big-bang se convierte en un flip de flag, no en un deploy al filo de la navaja.

Saber cuándo parar

La disciplina más difícil. Una checklist útil:

  • El propósito del refactor (¿onboarding más rápido? ¿habilitar feature X? ¿eliminar una clase de bugs?) ya es alcanzable. Declarar terminado es el objetivo, no seguir puliendo.
  • El costo por cambio volvió a subir: el próximo movimiento es difícil porque el código ya soporta lo que necesita.
  • El diff se convirtió en un nit cosmético de una línea en lugar de un movimiento estructural. Para.

Los ingenieros sobre-refactorizan cuando tratan al codebase como un objeto de arte en lugar de una herramienta. La mayoría de los refactors deberían parar justo antes de “perfecto”, porque el refactor marginal rara vez llega al cliente pero siempre cuesta el presupuesto de review-time del equipo.

Trayectoria de práctica

  1. Identifica una función “aterradora” que nadie quiere tocar en un codebase que uses. Escribe tres tests de caracterización para ella; describe qué cambia en tu disposición a editarla.
  2. Aplica Extract Interface a una dependencia legacy. Anota qué se vuelve testeable que antes no lo era.
  3. Dibuja un plan de migración strangler-fig para cualquier servicio que conozcas: nombra la capa router, los slices, la regla de rollback.
  4. Elige una mejora estancada en “primero hay que refactorizar” y aplica el método Mikado: dibuja el árbol de dependencias, encuentra una hoja que puedas enviar hoy.
  5. Corre una pasada de “¿qué borraría?” sobre el sistema después de tu refactor. La mayoría de refactors legacy dan restando código. Si el tuyo suma, pregunta por qué.

Cuándo es la herramienta correcta

SituaciónConclusión
Módulo de alto riesgo sin testsTests de caracterización primero; refactor después
Migración que necesita enviar productoBranch-by-abstraction; nunca congelar el desarrollo
Reemplazo con puntos de corte clarosStrangler fig; dirige slices pequeños primero
Refactor bloqueado por dependencias enredadasMétodo Mikado; nunca varar al equipo en estado roto
“Refactor listo, pero quiero seguir puliendo”Para. Envía. Refactoriza otra vez cuando aparezca la próxima necesidad real