Volver al inicio

Refactorización Segura de Módulos: Preservar Comportamiento | Prácticas TI

El artículo revela una estrategia para extraer módulos de forma segura de sistemas monolíticos. Describe principios de preservación de comportamiento, usando banderas de características y pruebas para prevenir regresiones. Énfasis en minimizar cambios en la primera etapa.

Refactorización Sin Fallos: Guía para Extraer Módulos
Advertisement 728x90

Refactorización sin riesgos: Cómo extraer módulos preservando el comportamiento

Cuando se mueve un módulo de un monolito a un servicio separado, el objetivo principal es preservar el comportamiento exactamente como está, incluyendo errores conocidos y peculiaridades no documentadas. Los intentos de "limpiar" el código en esta etapa a menudo llevan a regresiones. Te mostraremos cómo evitar las trampas usando una estrategia de cambios mínimos.

Por qué el enfoque 'aburrido' te salva de regresiones

Los sistemas en producción tienen un contrato implícito moldeado por años de operación: integraciones de clientes, manejo de errores, retrasos de tiempo y adaptaciones a APIs externas. Este contrato no está documentado, está enterrado en logs, commits y la experiencia del equipo. La refactorización no debería alterar el comportamiento, incluso si incluye errores. Por ejemplo, si el código antiguo devuelve una cadena vacía en lugar de null, cambiar eso romperá a los clientes que ya se han adaptado a esta lógica. Principio clave: la extracción cambia la forma pero preserva la semántica. Cualquier desviación del comportamiento original convierte la refactorización en una publicación con consecuencias impredecibles.

Un tipo de cambio a la vez: Límites antes de la estructura interna

Divide las tareas en etapas:

Google AdInline article slot
  • Etapa A: Extrae el módulo preservando el contrato (HTTP, cola, bus) e introduce una bandera de características.
  • Etapa B: Actualiza el stack, librerías o estilo de código.
  • Etapa C: Corrige errores.
  • Etapa D: Optimiza el rendimiento.

Mezclar etapas en una sola PR es una forma segura de diffs ilegibles, dolores de cabeza en rollback y discusiones sobre "¿es esto un error o una nueva función?". En la primera etapa, envuelve el código antiguo: nuevo límite de servicio, lógica antigua dentro. Por ejemplo, al extraer un módulo de enrutamiento, mantén la función original route() pero envuélvela en un endpoint HTTP. Esto asegura un comportamiento idéntico, incluso si el código parece "feo".

Bandera de características: Tu interruptor de emergencia

Ejecutar código antiguo y nuevo en paralelo en producción requiere un interruptor controlado vía config o variables de entorno. Esto proporciona:

  • Rollback instantáneo sin redeplegar durante un incidente.
  • Despliegue gradual a una porción del tráfico (lanzamiento canario).
  • Retención a largo plazo de la implementación antigua para comparación de comportamiento.

Críticamente, el interruptor debe funcionar sin reiniciar el servicio. De lo contrario, un incidente fuera de horas se convierte en una llamada de emergencia en lugar de un simple ajuste de config. Implementa la bandera al nivel de lógica de negocio, no de infraestructura, para evitar dependencias complejas en orquestadores.

Google AdInline article slot

Copia el comportamiento, no lo reescribe

Directriz para extracción segura: copia archivos "tal cual", cambiando solo lo necesario para el build. Ejemplo del mundo real, mover enrutamiento de un actor a una función pura:

object UpdateRouting {
  def route(update: Update): Either[RoutingError, RouteTarget] =
    if (update.message.exists(_.chat.isGroupOrSupergroup)) Right(GroupFlow)
    else if (update.callbackQuery.isDefined) Right(CallbackFlow)
    else Right(PrivateFlow)
}

Al mapear modelos, preserva incluso detalles sutiles. Si el código antiguo convertía un channelId faltante a una cadena vacía, haz lo mismo:

def toBusMessage(in: PlatformInMessage): BusIncoming =
  BusIncoming(
    correlationId = newCorrelationId(),
    channelId = in.channelId.getOrElse("")
  )

Cualquier cambio semántico (como intercambiar una cadena vacía por null) rompe el contrato con otros servicios. Tu objetivo: comportamiento idéntico bit a bit, incluso si el código parece anticuado.

Google AdInline article slot

Qué evitar absolutamente durante la extracción

Mientras la bandera de características está activa, evita:

  • Nuevas librerías. Cada una añade dependencias transitivas y superficie de ataque. Las actualizaciones de stack son una tarea separada.
  • Cambios de estilo de código. Mezclar código de dominio antiguo con wrappers nuevos es el costo temporal de la seguridad.
  • Corregir errores descubiertos. Registra un ticket y abórdalo después de la estabilización. De lo contrario, cada incidente se achaca a la refactorización.
  • Optimizaciones 'mientras estamos en el archivo'. El perfilado y la aceleración son un ciclo separado.

Estas restricciones pueden parecer excesivas, pero seis meses después en git blame verás una historia clara: "módulo extraído, conectado el bus", no "reescrito todo".

Pruebas: Bloqueando el contrato, no KPIs

Las pruebas unitarias en funciones puras (enrutamiento, mapeo) deben capturar el comportamiento antes de la extracción. Una prueba verde post-extracción garantiza lógica sin cambios. Para escenarios de integración (red, colas, BD), usa pruebas E2E, pero ten en cuenta sus límites:

  • Mocks de sistemas externos pueden perpetuar suposiciones erróneas del código.
  • APIs reales a menudo tienen peculiaridades sutiles (como un prefijo /api/) no cubiertas en pruebas.

Siempre incluye una ejecución manual en un entorno de staging similar a producción. La automatización no puede reemplazar verificaciones en condiciones casi reales.

El crecimiento de código no es un error, es una etapa del proceso

Después de la primera etapa, el volumen de código a menudo se hincha: adaptadores, banderas de características, duplicación del monolito. Eso es normal: estás pagando por rollbacks seguros y despliegues escalonados. La limpieza y reducción vienen en la etapa dos, una vez que el límite es estable, el código antiguo está eliminado y las pruebas confirman la corrección. No optimices por adelantado: asegúrate primero de que funcione.

Lecciones clave

  • Objetivo de extracción: transferir comportamiento "tal cual", no una mejora integral.
  • Bandera de características es obligatoria para rollback seguro sin deploy.
  • Pruebas bloquean el contrato antes de que comience la extracción, no después.
  • Prohibir cambios paralelos a stack, correcciones de errores y optimizaciones.
  • El código puede crecer: es parte de la estrategia, no un error.

Refactorizar código legado no es lugar para experimentos. Cuanto más aburrido el proceso, menos probable un despertar a las 3 de la mañana por caída en producción. Sigue estas reglas para convertir una extracción riesgosa en una operación predecible.

— Editorial Team

Advertisement 728x90

Leer después