Módulo 7: Modernizar Legacy Code

Refactoring Incremental vs Big Bang

Refactoring Incremental vs Big Bang

Descripción de la cápsula

Esta cápsula formaliza por qué incremental gana y cómo ejecutarlo como proceso. No es solo una preferencia — es una estrategia con ventajas medibles en riesgo, debugging, y rollback.


Por Qué Big Bang Falla

El escenario

Big Bang:
Lunes: "Voy a modernizar todo el módulo"
       Cambio f-strings + type hints + patterns + dead code
       en 15 archivos simultáneamente
Martes: 3 tests fallan
        ¿Cuál de los 4 tipos de cambio causó el fallo?
        ¿En cuál de los 15 archivos?
        → Debugging combinatorio: 4 tipos × 15 archivos = 60 posibles causas
Miércoles: Todavía debugging
Jueves: "Voy a revertir todo y empezar de nuevo"

El costo real

  • Debugging exponencial: N tipos de cambio × M archivos = N×M posibles causas
  • Rollback total: si algo falla, pierdes todos los cambios
  • Code review imposible: un PR con 15 archivos y 4 tipos de cambios es inrevisable
  • Merge conflicts: mientras trabajas, otros developers modifican los mismos archivos

Por Qué Incremental Gana

El escenario

Incremental:
Lunes AM:  f-strings en user_service.py → tests pass → commit
Lunes PM:  f-strings en order_service.py → tests pass → commit
Martes AM: type hints en user_service.py → tests pass → commit
Martes AM: type hints en order_service.py → 1 test falla
           → El fallo es por type hints en order_service.py
           → Sabemos exactamente qué y dónde
           → Fix en 10 minutos → commit

Las ventajas

CriterioBig BangIncremental
DebuggingN×M posibles causas1 causa obvia
RollbackTodo o nadaRevertir 1 commit
Code reviewImposible de revisar1 tipo de cambio por PR
Merge conflictsMuchosMínimos
Progreso visible0% hasta el finalX% cada día
RiesgoAltoBajo

El Framework Incremental

Paso 1: Priorizar

Lista de modernizaciones por prioridad:
1. Dead code removal (bajo riesgo, limpia ruido)
2. Import cleanup (bajo riesgo)
3. bare except → specific exceptions (medio impacto)
4. %-formatting → f-strings (bajo riesgo)
5. Type hints en funciones públicas (medio esfuerzo)
6. Manual classes → dataclasses (medio riesgo)
7. os.path → pathlib (medio riesgo)

Paso 2: Un tipo a la vez

Sprint 1: Dead code + import cleanup (todo el módulo)
Sprint 2: Exception handling (todo el módulo)
Sprint 3: f-strings (todo el módulo)
Sprint 4: Type hints (todo el módulo)
Sprint 5: Dataclasses + pathlib (todo el módulo)

Paso 3: Tests en cada paso

Para cada sprint:
1. Ejecutar tests antes del cambio → green
2. Hacer el cambio (1 tipo)
3. Ejecutar tests después → green
4. Commit con mensaje descriptivo
5. Si falla → fix inmediato (causa es obvia)

Implementación con Claude Code

El prompt incremental

> "Vamos a modernizar src/services/order_service.py
   INCREMENTALMENTE. Empezamos con el paso 1.
   
   PASO 1: Elimina dead code (imports no usados,
   funciones nunca llamadas, variables no referenciadas).
   
   Muestra los cambios. Ejecuta tests. Espera mi
   confirmación antes del paso 2."

Después de confirmar:

> "Tests pasan. Paso 2: Convierte bare except a
   specific exceptions. Muestra cambios. Ejecuta tests."

El anti-patrón con Claude Code

# MALO: pedir todo junto
> "Moderniza order_service.py: f-strings, type hints,
   dataclasses, pathlib, y limpia dead code"

# Claude Code hace todo junto → si falla, no sabes qué causó

# BUENO: un paso a la vez
> "Solo f-strings por ahora. Nada más."

Cuándo Big Bang es Aceptable

En raras ocasiones, big bang tiene sentido:

  1. El módulo es tiny (< 100 líneas) — el riesgo es mínimo
  2. Tienes 100% test coverage — puedes verificar todo
  3. Los cambios son puramente cosméticos — solo formatting, no lógica
  4. Estás reescribiendo de cero — no es modernización, es rewrite

Si no aplica ninguno de estos, usa incremental.


Conexión con Proyecto

En el Proyecto del Módulo (cápsula 05), ejecutas modernización incremental: un tipo de cambio a la vez, tests en cada paso, commit separado para cada tipo.


Ejercicios

Ejercicio 1: Ordenar modernizaciones (Fácil)

Ordena estas modernizaciones de menor a mayor riesgo:

  1. Agregar type hints a funciones
  2. Eliminar imports no usados
  3. Reemplazar bare except con specific exceptions
  4. Convertir classes manuales a dataclasses
  5. Eliminar funciones nunca llamadas
Ver solución
  1. Eliminar imports no usados (0 riesgo)
  2. Eliminar funciones nunca llamadas (bajo riesgo, verificar con grep)
  3. Convertir %-formatting a f-strings (bajo riesgo)
  4. Reemplazar bare except (medio riesgo, cambia error handling)
  5. Agregar type hints (bajo riesgo pero alto esfuerzo)
  6. Dataclasses (medio riesgo, cambia instanciación)

Ejercicio 2: Diseñar plan incremental (Medio)

Tu módulo tiene 8 archivos con tech debt mixto. Diseña un plan incremental de 4 sprints.

Ver solución
Sprint 1 (bajo riesgo, limpieza):
- Dead code removal en los 8 archivos
- Import cleanup en los 8 archivos
- 1 commit por tipo

Sprint 2 (bajo riesgo, syntax):
- f-strings en los 8 archivos
- 1 commit: "modernize string formatting"

Sprint 3 (medio riesgo, patterns):
- bare except → specific exceptions
- open() → context managers
- 1 commit por tipo

Sprint 4 (medio esfuerzo, types):
- Type hints en funciones públicas
- Dataclasses donde aplique
- 1 commit por tipo

El Costo Real: Tabla Comparativa

Para cuantificar la diferencia más allá de la intuición, considera un módulo típico de 500 líneas con 12 items de tech debt:

MétricaBig BangIncremental
Tiempo de implementación2-3 horas4-5 horas
Tiempo de debugging si falla4-12 horas (combinatorio)10-30 minutos por falla
Probabilidad de incident en producciónAlta (~30%)Muy baja (<5%)
Tiempo de code review2 horas (PR ilegible)10 min × 5 PRs = 50 min
Pérdida si hay que revertirTodo el trabajoSolo el último commit
Costo total esperado8-15+ horas5-6 horas

Big bang parece más rápido en superficie (2-3 horas vs 4-5). Pero el costo total incluye debugging, review, y riesgo de incident. Cuando integras todos los costos, incremental es 30-50% más eficiente — y radicalmente menos arriesgado.


Errores Comunes con Incremental

Error 1: "Incremental" pero todo en un solo commit

Síntoma: Aplicaste incremental en pasos, pero al final hiciste git add . && git commit. El historial muestra 1 commit con 50 cambios.

Por qué pasa: El instinto de "limpiar el historial" sobreescribe la disciplina incremental. Pero el historial es el valor.

Cómo corregir: Un commit por paso. Si necesitas combinarlos al final, usa git rebase --interactive para fusionar selectivamente, no para colapsar todo.

Error 2: Tests verdes pero no cubren el cambio

Síntoma: Modernizaste un patrón. Tests verdes. En producción falla. Resulta que los tests no tocan el código que cambiaste.

Por qué pasa: "Tests verdes" se confunde con "tests verdes que cubren mi cambio". Si modificas función X y los tests no llaman X, los tests son irrelevantes.

Cómo corregir: Antes de avanzar al siguiente paso, verifica coverage del cambio. Si modificaste líneas que no tienen tests, escribe el test antes de modernizar.

Error 3: Pasos demasiado grandes ("incremental pero gordo")

Síntoma: "Paso 1: modernizar todo lo de syntax". Pero "todo syntax" son f-strings + type comparisons + dict comprehensions + walrus + etc. Si falla, ¿cuál fue?

Por qué pasa: Pereza intelectual de definir el "tipo" de cambio.

Cómo corregir: Un tipo específico de cambio por commit. F-strings es un commit. type() == X → isinstance es otro. No "syntax modernization" como categoría general.

Error 4: Saltar la fase de tests previos

Síntoma: Empiezas con el cambio, "los tests existentes son suficientes". Después no sabes si lo que romperás existía antes o lo introdujiste tú.

Por qué pasa: Escribir tests se siente como "tarea aparte" del refactoring real.

Cómo corregir: Tests siempre son el paso 1. Aunque ya existan tests, ejecuta la suite y confirma que está verde antes de tocar nada.

Error 5: No commit cuando un test queda rojo "temporalmente"

Síntoma: Hiciste un cambio, un test falla "pero lo voy a arreglar ya". Pasan 2 horas, tienes 5 cambios encimados, y revertir es imposible.

Por qué pasa: Optimismo: "ya casi lo arreglo". Pero los problemas se acumulan, no se simplifican.

Cómo corregir: Si los tests quedan rojos al cabo de 15 minutos, revierte y replantea. No acumules más cambios encima de un fallo.


Resumen

  • Incremental gana por debugging, rollback, review, y riesgo
  • Big Bang falla porque N cambios × M archivos = debugging imposible
  • El framework: priorizar → un tipo a la vez → tests en cada paso → commit
  • Claude Code debe recibir instrucciones incrementales, no "moderniza todo"
  • Big bang aceptable solo en módulos tiny con 100% coverage
  • Costo total de incremental es ~30-50% menor que big bang
  • Tests verdes ≠ tests que cubren tu cambio — verifica coverage del cambio

Próxima cápsula: Proyecto — Modernización de Módulo Legacy.


Una Reflexión: Por Qué el Instinto Te Engaña

El instinto humano dice: "si hago todo junto, ahorro tiempo". Es la misma lógica de "voy a hacer esta semana 5 cosas a la vez" que produce el resultado opuesto al esperado.

En programación, el instinto es especialmente engañoso con AI tools. Claude Code puede generar código rápido — y eso refuerza el instinto: "puedo cambiar 10 cosas en un solo prompt". Sí puedes. Pero el costo no está en generar — está en debugging cuando algo falla.

Tiempo para que Claude Code haga 10 cambios:    5 minutos
Tiempo para verificar que pasen los tests:       2 minutos
Tiempo de debugging si UN cambio falla:          30 min - 4 horas
                                                (depende de cuál de los 10)

Tiempo total esperado:                           38 min - 4h 7min

Versus incremental:
Tiempo para que Claude Code haga 1 cambio:       30 segundos
Tiempo para verificar tests:                     30 segundos
Tiempo de debugging si falla:                    5-15 min (causa obvia)
Repite 10 veces:                                 10 × 1 min + 1 fallo × 10 min = 20 min total

Incremental gana incluso si todo sale bien, pero gana abrumadoramente cuando algo falla. El instinto te engaña porque solo considera el caso "todo sale bien" — y en proyectos reales, algo falla.


Recursos Adicionales

  1. Working Effectively with Legacy Code - Feathers sobre cambios incrementales
  2. Ship Small PRs - Google engineering practices
  3. Trunk Based Development - Commits frecuentes y pequeños
  4. Feature Flags for Gradual Rollout - Para modernizaciones en producción