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:

  1. God file: todo en app.py (500+ líneas)
  2. Validación inline duplicada (users y orders validan igual)
  3. Cálculo de precios inline (debería ser función separada)
  4. Utils mezcladas con routes (format_price, validate_email)
  5. 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

  1. Refactoring Guru - Catalog - Referencia de todos los tipos de refactoring con ejemplos
  2. pytest Documentation - Para escribir tests de regresión
  3. FastAPI Testing - Testing de endpoints FastAPI con TestClient
  4. Git Best Practices for Refactoring - Commits atómicos y mensajes descriptivos
  5. 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.