Módulo 4: Refactoring Multi-File Coordinado
Proyecto del Módulo: Refactoring Coordinado
Proyecto del Módulo: Refactoring Coordinado
Descripción del proyecto
Este proyecto es la prueba de que puedes ejecutar refactoring multi-file de forma profesional. Vas a tomar un codebase con problemas estructurales reales — lógica mezclada, naming inconsistente, duplicación, god objects — y lo vas a mejorar usando las cuatro técnicas que aprendiste: rename, extract, move, e interface changes. Todo coordinado con Claude Code y respaldado por tests de regresión.
La diferencia con los ejercicios de las cápsulas anteriores es la escala y la integración. Aquí no aplicas una técnica aislada — combinas varias en secuencia para lograr una mejora estructural significativa. El refactoring toca 5+ archivos y requiere coordinación real.
El escenario simula un ticket real: "Refactorizar el módulo de órdenes para separar responsabilidades y mejorar mantenibilidad." No es un rewrite — es mejora incremental de lo que existe, preservando comportamiento.
Este proyecto conecta directamente con el Módulo 8 (Proyecto Integrador), donde el refactoring es uno de los pasos de la migración completa de un proyecto legacy.
Objetivo del Proyecto
Ejecutar refactoring coordinado en un codebase real que toca 5+ archivos, usando Claude Code como coordinador y tests de regresión como safety net.
Al completar este proyecto:
- ✅ Habrás ejecutado al menos 3 tipos de refactoring (rename, extract, move, o interface change)
- ✅ Habrás escrito tests de regresión ANTES de cada refactoring
- ✅ Habrás verificado que tests pasan DESPUÉS de cada refactoring
- ✅ Habrás mejorado la estructura de un codebase sin cambiar su comportamiento
- ✅ Habrás documentado cada cambio con justificación
Especificaciones Técnicas
Codebase a Refactorizar
Opción A — Codebase proporcionado (recomendada):
Crea un mini-proyecto con estos problemas intencionados:
mkdir refactoring-project && cd refactoring-project
python -m venv venv && source venv/bin/activate
pip install fastapi uvicorn pydantic pytest
Crea estos archivos con los problemas estructurales que vas a refactorizar:
src/app.py — God object con lógica mezclada:
from fastapi import FastAPI, HTTPException
import json
from datetime import datetime
app = FastAPI()
DB = {"users": {}, "orders": {}, "products": {}}
NEXT_ID = {"users": 1, "orders": 1}
# === Todo mezclado en un solo archivo ===
@app.post("/users")
def create_user(data: dict):
# Validación inline (debería estar en validators)
if not data.get("name") or len(data["name"]) < 2:
raise HTTPException(400, "Invalid name")
if not data.get("email") or "@" not in data["email"]:
raise HTTPException(400, "Invalid email")
for u in DB["users"].values():
if u["email"] == data["email"]:
raise HTTPException(409, "Email exists")
uid = NEXT_ID["users"]
NEXT_ID["users"] += 1
user = {"id": uid, "name": data["name"], "email": data["email"],
"created_at": datetime.now().isoformat()}
DB["users"][uid] = user
return user
@app.get("/users/{user_id}")
def get_user(user_id: int):
if user_id not in DB["users"]:
raise HTTPException(404, "User not found")
return DB["users"][user_id]
@app.post("/orders")
def create_order(data: dict):
# Validación inline (duplicada con create_user pattern)
if not data.get("user_id"):
raise HTTPException(400, "Missing user_id")
if data["user_id"] not in DB["users"]:
raise HTTPException(404, "User not found")
if not data.get("items") or len(data["items"]) == 0:
raise HTTPException(400, "No items")
# Cálculo inline (debería ser su propia función)
subtotal = 0
for item in data["items"]:
if item.get("product_id") not in DB["products"]:
raise HTTPException(404, f"Product {item.get('product_id')} not found")
product = DB["products"][item["product_id"]]
subtotal += product["price"] * item.get("quantity", 1)
tax = subtotal * 0.16
shipping = 9.99 if subtotal < 50 else 0
total = subtotal + tax + shipping
oid = NEXT_ID["orders"]
NEXT_ID["orders"] += 1
order = {"id": oid, "user_id": data["user_id"], "items": data["items"],
"subtotal": round(subtotal, 2), "tax": round(tax, 2),
"shipping": round(shipping, 2), "total": round(total, 2),
"status": "pending", "created_at": datetime.now().isoformat()}
DB["orders"][oid] = order
return order
@app.get("/orders/{order_id}")
def get_order(order_id: int):
if order_id not in DB["orders"]:
raise HTTPException(404, "Order not found")
return DB["orders"][order_id]
@app.post("/products")
def create_product(data: dict):
pid = data.get("id", len(DB["products"]) + 1)
product = {"id": pid, "name": data["name"], "price": data["price"]}
DB["products"][pid] = product
return product
# Función utilitaria que debería estar en utils/
def format_price(amount):
return f"${amount:.2f}"
# Otra función que debería estar en utils/
def validate_email(email):
return "@" in email and "." in email.split("@")[1]
Opción B — Tu propio proyecto:
Si tienes un proyecto real con problemas similares, úsalo. Requisitos: al menos 3 archivos, al menos 2 problemas estructurales claros.
Herramientas
- Claude Code
- pytest para tests de regresión
- Git para tracking de cambios (1 commit por refactoring)
Plan de Refactoring (5 pasos)
Paso 1: Assessment (identificar problemas)
Usa Claude Code para analizar el codebase:
> "Analiza src/app.py e identifica problemas estructurales:
god objects, lógica mezclada, duplicación, funciones que
deberían estar en otros archivos. Lista cada problema
con severidad y sugerencia de refactoring."
Problemas esperados:
- God file: todo en
app.py(500+ líneas) - Validación inline duplicada (users y orders validan igual)
- Cálculo de precios inline (debería ser función separada)
- Utils mezcladas con routes (
format_price,validate_email) - No hay separación de layers (routes, services, validators)
Paso 2: Tests de regresión (ANTES de cambiar nada)
> "Antes de cualquier refactoring, escribe tests de regresión
completos para todos los endpoints: create_user, get_user,
create_order, get_order, create_product. Incluye happy paths,
validaciones, y error handling. Ejecuta y confirma green."
Paso 3: Refactoring 1 — Extract utils
> "Extrae format_price() y validate_email() de app.py a
un nuevo archivo src/utils.py. Actualiza imports en app.py.
Ejecuta tests."
Paso 4: Refactoring 2 — Extract services
> "Extrae la lógica de negocio de create_order() a una nueva
clase OrderService en src/services/order_service.py.
OrderService debe tener métodos: validate_order(),
calculate_total(), create(). El endpoint en app.py solo
llama a OrderService. Ejecuta tests."
Paso 5: Refactoring 3 — Extract validators + Rename
> "Extrae toda la lógica de validación a src/validators.py.
Crea funciones: validate_user_data(), validate_order_data().
Elimina la validación duplicada en los endpoints.
Renombra las funciones de endpoints para claridad:
create_user → handle_create_user (route handler).
Ejecuta tests."
Entregable
Estructura esperada después del refactoring
refactoring-project/
├── src/
│ ├── app.py # Solo routes (delgado)
│ ├── services/
│ │ └── order_service.py # Lógica de negocio
│ ├── validators.py # Validación centralizada
│ └── utils.py # Utilidades puras
├── tests/
│ ├── test_regression.py # Tests de regresión originales
│ ├── test_order_service.py # Tests del servicio extraído
│ └── test_validators.py # Tests de validators
├── REFACTORING_LOG.md # Documentación de cambios
└── requirements.txt
REFACTORING_LOG.md
# Refactoring Log
## Assessment
- [Lista de problemas encontrados con severidad]
## Refactoring 1: Extract Utils
- **Qué:** Mover format_price y validate_email a utils.py
- **Por qué:** Funciones utilitarias no pertenecen en el archivo de routes
- **Archivos afectados:** app.py, utils.py (nuevo)
- **Tests:** ✅ Todos pasan antes y después
## Refactoring 2: Extract OrderService
- **Qué:** Extraer lógica de órdenes a OrderService
- **Por qué:** app.py es un god file, la lógica de negocio debe estar separada
- **Archivos afectados:** app.py, services/order_service.py (nuevo)
- **Tests:** ✅ Todos pasan antes y después
## Refactoring 3: Extract Validators + Rename
- **Qué:** Centralizar validación, renombrar handlers
- **Por qué:** Validación duplicada, naming inconsistente
- **Archivos afectados:** app.py, validators.py (nuevo)
- **Tests:** ✅ Todos pasan antes y después
## Métricas
| Métrica | Antes | Después |
|---------|-------|---------|
| Archivos | 1 | 5 |
| Líneas en app.py | ~100 | ~40 |
| Funciones duplicadas | 2 | 0 |
| Tests | 0 | 15+ |
Criterios de Éxito
Tu proyecto está completo cuando:
- ✅ Al menos 3 refactoring diferentes ejecutados (rename, extract, move, o interface change)
- ✅ Tests de regresión escritos ANTES de cada refactoring
- ✅ Tests pasan DESPUÉS de cada refactoring
- ✅ El codebase tiene mejor estructura sin cambio de comportamiento
- ✅ REFACTORING_LOG.md documenta cada cambio con justificación
- ✅ Al menos 5 archivos fueron afectados en total
- ✅ Git history muestra 1 commit por refactoring (no todo junto)
Rúbrica de Evaluación (100 puntos)
Tests de Regresión (30 puntos)
- (10 pts) Tests escritos ANTES de cada refactoring
- (10 pts) Tests cubren happy paths, validaciones, y errors
- (10 pts) Tests pasan antes Y después de cada refactoring
Calidad del Refactoring (40 puntos)
- (10 pts) Al menos 3 tipos de refactoring aplicados
- (10 pts) Comportamiento preservado (tests green en cada paso)
- (10 pts) Estructura mejorada significativamente
- (10 pts) 5+ archivos afectados con cambios coherentes
Documentación (20 puntos)
- (10 pts) REFACTORING_LOG completo con assessment, cambios, y métricas
- (5 pts) Git history limpio (1 commit por refactoring)
- (5 pts) Cada refactoring tiene justificación clara
Proceso (10 puntos)
- (5 pts) Orden de refactoring lógico (menor a mayor riesgo)
- (5 pts) Uso efectivo de Claude Code como coordinador
Extra Credit (hasta +10 puntos)
- (+3 pts) 4+ tipos de refactoring aplicados
- (+3 pts) Tests de regresión + tests unitarios para código nuevo
- (+2 pts) Métricas de antes/después (líneas, complejidad)
- (+2 pts) Diagrama de arquitectura antes/después
Errores Comunes
Error 1: Refactorizar sin tests primero
El error #1. Si un test falla después y no tenías tests antes, no sabes si el refactoring lo causó o si ya estaba roto.
Error 2: Hacer todos los refactoring en un solo commit
Si algo falla, no puedes saber cuál refactoring lo causó. Un commit por refactoring permite revertir solo el problemático.
Error 3: Cambiar comportamiento durante el refactoring
"Ya que estoy aquí, voy a arreglar este bug también." No. Refactoring y bug fix son commits separados. Mezclarlos hace imposible verificar que el refactoring preservó comportamiento.
Error 4: No verificar tests después de CADA refactoring
Verificar solo al final acumula errores. Si el refactoring 2 rompió algo, pero sigues con el 3, ahora tienes 2 problemas encimados.
Error 5: Estructura final peor que la original
A veces el refactoring crea más archivos de los necesarios, o separa cosas que deberían estar juntas. Verifica que la estructura final tiene sentido, no solo que los tests pasan.
Error 6: No documentar el "por qué"
"Moví X a Y" sin explicar por qué es documentación inútil. "Moví X a Y porque X contenía lógica de negocio que no pertenece en el layer de routing" es documentación útil.
Error 7: Refactoring demasiado ambicioso
Un refactoring que toca 20 archivos tiene alto riesgo. Prefiere 3 refactoring de 5-7 archivos cada uno a 1 refactoring masivo.
Recursos para el Proyecto
- Refactoring Guru - Catalog - Referencia de todos los tipos de refactoring con ejemplos
- pytest Documentation - Para escribir tests de regresión
- FastAPI Testing - Testing de endpoints FastAPI con TestClient
- Git Best Practices for Refactoring - Commits atómicos y mensajes descriptivos
- Martin Fowler - Refactoring - El sitio del autor de la biblia de refactoring
Conexión con Siguiente Módulo
Lo que aprendiste aquí — refactoring dentro de un mismo framework — se escala en el Módulo 5: Migración de Frameworks. La transición es:
"Ya sabes refactorizar dentro de un mismo framework — rename, extract, move, change interface. Pero ¿qué pasa cuando necesitas cambiar de framework? Flask→FastAPI, sync→async, unittest→pytest. Eso es migración: refactoring a escala de framework completo."
Las técnicas son las mismas (tests primero, cambios coordinados, verificación). La escala es mayor.