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
| Criterio | Big Bang | Incremental |
|---|---|---|
| Debugging | N×M posibles causas | 1 causa obvia |
| Rollback | Todo o nada | Revertir 1 commit |
| Code review | Imposible de revisar | 1 tipo de cambio por PR |
| Merge conflicts | Muchos | Mínimos |
| Progreso visible | 0% hasta el final | X% cada día |
| Riesgo | Alto | Bajo |
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:
- El módulo es tiny (< 100 líneas) — el riesgo es mínimo
- Tienes 100% test coverage — puedes verificar todo
- Los cambios son puramente cosméticos — solo formatting, no lógica
- 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:
- Agregar type hints a funciones
- Eliminar imports no usados
- Reemplazar bare except con specific exceptions
- Convertir classes manuales a dataclasses
- Eliminar funciones nunca llamadas
Ver solución
- Eliminar imports no usados (0 riesgo)
- Eliminar funciones nunca llamadas (bajo riesgo, verificar con grep)
- Convertir %-formatting a f-strings (bajo riesgo)
- Reemplazar bare except (medio riesgo, cambia error handling)
- Agregar type hints (bajo riesgo pero alto esfuerzo)
- 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étrica | Big Bang | Incremental |
|---|---|---|
| Tiempo de implementación | 2-3 horas | 4-5 horas |
| Tiempo de debugging si falla | 4-12 horas (combinatorio) | 10-30 minutos por falla |
| Probabilidad de incident en producción | Alta (~30%) | Muy baja (<5%) |
| Tiempo de code review | 2 horas (PR ilegible) | 10 min × 5 PRs = 50 min |
| Pérdida si hay que revertir | Todo el trabajo | Solo el último commit |
| Costo total esperado | 8-15+ horas | 5-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
- Working Effectively with Legacy Code - Feathers sobre cambios incrementales
- Ship Small PRs - Google engineering practices
- Trunk Based Development - Commits frecuentes y pequeños
- Feature Flags for Gradual Rollout - Para modernizaciones en producción