Módulo 7: Modernizar Legacy Code
Proyecto del Módulo: Modernización de Módulo Legacy
Proyecto del Módulo: Modernización de Módulo Legacy
Descripción del proyecto
Vas a modernizar un módulo Python con al menos 5 items de tech debt. Cada cambio tiene test de regresión y commit separado. El entregable es el módulo modernizado + un change log detallado.
Este proyecto es directamente parte del Módulo 8 (Proyecto Integrador) donde la modernización es uno de los pasos de la migración completa de un proyecto legacy.
Objetivo del Proyecto
Modernizar un módulo legacy de forma incremental, preservando comportamiento con tests en cada paso.
Al completar:
- ✅ Habrás ejecutado un tech debt scan completo
- ✅ Habrás priorizado items con la matriz impacto/riesgo
- ✅ Habrás modernizado al menos 5 items de tech debt
- ✅ Cada modernización tiene tests de regresión
- ✅ Cada modernización tiene commit separado
- ✅ Change log documenta cada cambio
Especificaciones Técnicas
Módulo a Modernizar
Crea este módulo legacy con tech debt intencional:
# src/legacy_module.py
import os
import sys
import json
from datetime import datetime, timedelta
CACHE = {}
API_URL = "https://old-api.example.com"
def get_data(type, id):
"""Get data from cache or API."""
key = "%s_%s" % (type, id)
if CACHE.get(key):
return CACHE[key]
try:
# Simulate API call
if type == "user":
data = {"id": id, "name": "User %d" % id, "type": type}
elif type == "product":
data = {"id": id, "name": "Product %d" % id, "price": id * 10.0}
else:
data = None
if data:
CACHE[key] = data
return data
except:
return None
def process_items(items):
results = []
for i in range(len(items)):
item = items[i]
if item.get("active") == True:
total = item["price"] * item["quantity"]
tax = total * 0.16
if item.get("region") == "EU":
tax = total * 0.21
elif item.get("region") == "UK":
tax = total * 0.20
final = total + tax
result = {
"name": item["name"],
"total": final,
"tax": tax,
"processed_at": str(datetime.now())
}
results.append(result)
return results
def save_to_file(data, filename):
path = os.path.join(os.getcwd(), "output", filename)
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
f = open(path, "w")
f.write(json.dumps(data, indent=2))
f.close()
return path
def load_from_file(filename):
path = os.path.join(os.getcwd(), "output", filename)
if os.path.exists(path):
f = open(path, "r")
data = json.loads(f.read())
f.close()
return data
return None
def old_format_price(amount):
"""Deprecated: no longer used."""
return "$%.2f" % amount
def old_validate(data):
"""This function is never called anywhere."""
if data is None:
return False
if type(data) == dict:
return len(data) > 0
return True
class Config:
def __init__(self, host, port, debug, timeout):
self.host = host
self.port = port
self.debug = debug
self.timeout = timeout
def __repr__(self):
return "Config(host=%s, port=%s)" % (self.host, self.port)
def __eq__(self, other):
return (self.host == other.host and self.port == other.port
and self.debug == other.debug and self.timeout == other.timeout)
Tech Debt en Este Módulo
- Dead code:
import sysno usado,old_format_price(),old_validate(),API_URLno referenciado - %-formatting: 5+ instancias de
%sy%d - bare except:
except:sin especificar excepción - No context managers:
open()sinwith - os.path: debería ser pathlib
- No type hints: ninguna función tiene type hints
- Magic strings/numbers:
0.16,0.21,"EU","UK"hardcodeados type() == X: debería serisinstance()range(len()): debería ser iteración directa- Config class: candidata a dataclass
== True: comparación explícita innecesaria
Plan de Modernización (5 pasos mínimos)
Paso 1: Tests de regresión
> "Escribe tests de regresión para legacy_module.py:
get_data(), process_items(), save_to_file(),
load_from_file(), y Config. Ejecuta y confirma green."
Paso 2: Dead code removal
Eliminar: import sys, old_format_price(), old_validate(), API_URL.
Paso 3: Syntax modernization
%-formatting → f-strings, type() == X → isinstance(), range(len()) → iteración directa, == True → booleano directo.
Paso 4: Pattern modernization
open() → context managers, os.path → pathlib, bare except → specific exceptions, magic numbers → constants/enum.
Paso 5: Type hints + dataclass
Agregar type hints a funciones públicas. Config → dataclass.
Entregable
CHANGE_LOG.md
# Change Log: legacy_module.py Modernization
## Step 1: Tests de Regresión
- 12 tests escritos cubriendo todas las funciones
- All green ✅
## Step 2: Dead Code Removal
- Removed: import sys, old_format_price(), old_validate(), API_URL
- Tests: ✅ (12/12 pass)
- Commit: "remove dead code from legacy_module"
## Step 3: Syntax Modernization
- Changed: 5x %-formatting → f-strings
- Changed: 2x type() → isinstance()
- Changed: 1x range(len()) → direct iteration
- Changed: 1x == True → boolean
- Tests: ✅ (12/12 pass)
- Commit: "modernize syntax in legacy_module"
## Step 4: Pattern Modernization
- Changed: 2x open() → with statement (pathlib)
- Changed: 1x bare except → except Exception
- Changed: os.path → pathlib throughout
- Added: TAX_RATES constant dict for magic numbers
- Tests: ✅ (12/12 pass)
- Commit: "modernize patterns in legacy_module"
## Step 5: Type Hints + Dataclass
- Added: type hints to all public functions
- Changed: Config class → @dataclass
- Tests: ✅ (12/12 pass)
- Commit: "add type hints and convert Config to dataclass"
## Metrics
| Metric | Before | After |
|--------|--------|-------|
| Lines | 95 | 78 |
| Dead code items | 4 | 0 |
| %-formatting | 5 | 0 |
| Type hints | 0% | 100% public |
| Context managers | 0 | 2 |
| Tech debt items | 11 | 0 |
Criterios de Éxito
- ✅ Tech debt scan documenta al menos 5 items
- ✅ Al menos 5 modernizaciones ejecutadas
- ✅ Tests de regresión escritos ANTES de cualquier cambio
- ✅ Tests pasan después de CADA paso
- ✅ 1 commit por tipo de modernización
- ✅ CHANGE_LOG.md documenta cada cambio con métricas
Rúbrica de Evaluación (100 puntos)
Tech Debt Scan (20 puntos)
- (10 pts) Inventario completo con tipos y severidades
- (10 pts) Priorización con justificación
Modernización (40 puntos)
- (8 pts) Dead code eliminado
- (8 pts) Syntax modernizada (f-strings, isinstance, etc.)
- (8 pts) Patterns modernizados (context managers, pathlib)
- (8 pts) Type hints agregados
- (8 pts) Al menos 1 modernización adicional (dataclass, enums)
Testing (25 puntos)
- (10 pts) Tests escritos ANTES de cambios
- (10 pts) Tests pasan después de CADA paso
- (5 pts) Coverage de funciones principales
Documentación (15 puntos)
- (10 pts) CHANGE_LOG con detalle por paso
- (5 pts) Métricas before/after
Extra Credit (+10 puntos)
- (+3 pts) 7+ modernizaciones ejecutadas
- (+3 pts) Git history limpio con mensajes descriptivos
- (+2 pts) Comparison de legibilidad before/after
- (+2 pts) Todas las funciones tienen docstrings
Errores Comunes
- Modernizar sin tests primero — sin safety net, no sabes si rompiste algo
- Mezclar tipos de modernización — f-strings + type hints en el mismo commit
- Eliminar "dead code" que en realidad se usa — verifica con grep antes de eliminar
- Over-modernizar — no todo necesita ser dataclass o usar walrus operator
- No documentar — el CHANGE_LOG es parte del entregable
Recursos para el Proyecto
- pyupgrade - Para verificar tu modernización
- vulture - Para verificar dead code
- mypy - Para verificar type hints
- pytest - Para tests de regresión
- ruff - Linter ultra-rápido para Python
- pathlib documentation - Para reemplazar os.path
¿Qué Hacer si Te Atoras?
Si no sabes por dónde empezar:
→ Ejecuta el tech debt scan PRIMERO (cápsula 02)
→ Sin scan, modernizar es subjetivo
→ El scan te da el orden de priorización
Si los tests no pasan después del paso 2 (dead code):
→ Algo del "dead code" no era dead
→ Revierte el último cambio
→ Verifica con grep que la función realmente no se usa
→ Si se usa dinámicamente, márcala como "candidate" no "dead"
Si te tienta hacer todos los cambios juntos para "ahorrar tiempo":
→ Lee la cápsula 04 otra vez
→ El costo total de big bang es 30-50% MAYOR, no menor
→ Resiste la tentación
Si Claude Code "termina" la modernización en un solo paso:
→ Detente. Revierte. Pídele explícitamente: "solo el paso N"
→ No aceptes el primer output si no respeta la incrementalidad
→ Esto es ejercer trust calibration (guía #1 M5)
Si CHANGE_LOG.md se siente como burocracia:
→ Es el entregable que demuestra el proceso
→ Sin él, no puedes probar que fue incremental
→ 5 minutos por paso de documentación = 25 min total para 5 pasos
Evidencia de Éxito (Auto-Verificación)
Antes de declarar el proyecto completo, valida que cumples estos checkpoints:
Tech Debt Scan
- ✅ Inventario tiene al menos 5 items con tipo, severidad, y fix sugerido
- ✅ Priorización justificada con la matriz impacto/riesgo (cápsula 02)
- ✅ Items de seguridad identificados (si aplican) marcados como crítico/alto
Tests
- ✅ Los tests de regresión existen ANTES del primer cambio
- ✅ Cada paso cierra con tests verdes — no con "los voy a arreglar después"
- ✅ La suite completa pasa al final del paso 5
Modernización
- ✅ 5+ tipos distintos de modernización ejecutados
- ✅ 5+ commits separados (uno por tipo)
- ✅ Cada commit tiene mensaje descriptivo (no "WIP", "fix", "modernize")
Documentación
- ✅ CHANGE_LOG.md tiene una sección por cada paso
- ✅ Cada sección documenta: qué se cambió, por qué, tests resultantes
- ✅ Tabla de métricas before/after presente y precisa
Calidad
- ✅ El módulo modernizado pasa
pyupgrade --py310-plussin más cambios - ✅ El módulo modernizado pasa
vulturesin reportar dead code - ✅ Todas las funciones públicas tienen type hints (si el proyecto los usa)
Si los 12 puntos están en su lugar, el proyecto demuestra modernización profesional. Si dudas en alguno, vuelve a la cápsula correspondiente.
Conexión con Siguiente Módulo
Este proyecto cierra el módulo de modernización. El Módulo 8: Proyecto Integrador toma todas las técnicas de la guía completa — onboarding, exploración, architecture analysis, refactoring, migración, context management, y modernización — y las aplica en una migración de punta a punta de un proyecto legacy real.
Lo que hiciste aquí (modernización de un módulo) es un paso del proyecto integrador. Allá vas a modernizar 3-5 módulos como parte de un workflow más amplio que incluye también architecture analysis, migración de framework, y handoff documentation.