Módulo 4: Refactoring Multi-File Coordinado

Rename y Extract Refactoring Cross-File

Rename y Extract Refactoring Cross-File

Descripción de la cápsula

Rename y extract son los dos refactoring más frecuentes en el trabajo diario. Un rename cambia el nombre de una función, clase, o variable y actualiza todas las references en el codebase. Un extract toma un bloque de lógica dentro de una función y lo convierte en una función o clase separada. Ambos suenan simples — pero cuando tocan 10, 15, o 20 archivos, la complejidad se multiplica.

En esta cápsula vas a ejecutar ambos refactoring con Claude Code como coordinador. La diferencia con hacerlo manualmente o con un IDE es que Claude Code entiende el contexto semántico: no solo busca y reemplaza texto, sino que entiende qué references son del mismo concepto y cuáles son coincidencias de nombre. Y cuando extraes lógica, Claude Code crea el nuevo módulo, mueve el código, agrega imports, y actualiza callers — todo coordinado.

Antes de cada refactoring, escribes tests de regresión. Después, verificas que pasan. Ese ciclo es no negociable.


Rename Refactoring

Por qué naming importa tanto

Un nombre incorrecto o inconsistente multiplica el costo de entender código. Si una función se llama process_data() pero lo que hace es validar emails, cada developer que la lee pierde tiempo descifrando la discrepancia. Rename no cambia comportamiento — cambia comprensión.

El problema del rename multi-file

# El rename más simple: una función usada en 1 archivo
# → El IDE lo resuelve en 2 segundos

# El rename real: una clase usada en 12 archivos
# src/services/user_handler.py  → class UserHandler:
# src/api/routes/users.py       → from services.user_handler import UserHandler
# src/api/routes/admin.py       → from services.user_handler import UserHandler
# src/services/order_service.py → from services.user_handler import UserHandler
# src/tasks/cleanup.py          → from services.user_handler import UserHandler
# src/tasks/reports.py          → from services.user_handler import UserHandler
# tests/test_user_handler.py    → from services.user_handler import UserHandler
# tests/test_orders.py          → from services.user_handler import UserHandler
# tests/test_admin.py           → from services.user_handler import UserHandler
# docs/api_reference.md         → Menciona UserHandler
# config/services.yaml          → user_handler: enabled
# README.md                     → "UserHandler manages..."

Son 12 archivos. Olvidar uno = import error en runtime. Un IDE puede resolver los imports de Python, pero no actualiza YAML, markdown, ni comentarios. Claude Code actualiza todo.

Ciclo de rename con Claude Code

Paso 1: Tests de regresión

# Antes de renombrar, asegúrate de que los tests pasan:
> "Ejecuta los tests del proyecto y confirma que todos pasan"

# Output esperado:
# ✅ 47 tests passed, 0 failed
# Si hay tests fallando ANTES del rename, arregla eso primero

Paso 2: Identificar todas las references

# Pide a Claude Code que encuentre TODAS las references:
> "Encuentra todas las references a UserHandler en el proyecto.
   Incluye imports, uso directo, strings, YAML, markdown,
   comentarios, y docstrings. Lista cada archivo y línea."

# Output esperado:
# Python imports: 8 archivos
# Uso directo: 15 references en 8 archivos
# Strings/YAML: 2 references
# Markdown/docs: 3 references
# Comentarios: 4 references
# Total: 32 references en 12 archivos

Paso 3: Ejecutar el rename

# Da la instrucción completa:
> "Renombra UserHandler a UserService en todo el proyecto.
   Actualiza:
   1. La definición de la clase
   2. El nombre del archivo (user_handler.py → user_service.py)
   3. Todos los imports
   4. Todas las references en código
   5. References en YAML, markdown, y comentarios
   6. Nombres de tests (test_user_handler → test_user_service)"

# Claude Code coordina los cambios en todos los archivos

Paso 4: Verificar

# Ejecuta los tests:
> "Ejecuta los tests y confirma que todos pasan después
   del rename"

# Output esperado:
# ✅ 47 tests passed, 0 failed
# Si falla alguno → Claude Code olvidó una reference → arreglar

Ejemplo progresivo de rename

Básico — Rename de función:

# Antes: función con nombre vago
# src/utils/helpers.py
def process(data):
    """Valida que el email tenga formato correcto."""
    import re
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, data))

# Prompt a Claude Code:
> "Renombra la función process() en src/utils/helpers.py
   a validate_email(). Actualiza todas las references
   en el proyecto."

# Después:
def validate_email(data):
    """Valida que el email tenga formato correcto."""
    import re
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, data))

# + actualiza todos los callers:
# src/services/user_service.py: process(email) → validate_email(email)
# src/api/routes/auth.py: process(data["email"]) → validate_email(data["email"])
# tests/test_helpers.py: test_process() → test_validate_email()

Intermedio — Rename de clase + archivo:

# Prompt:
> "Renombra la clase DataProcessor en src/processing/data_processor.py
   a OrderCalculator. También renombra el archivo a order_calculator.py.
   Actualiza todos los imports, references, tests, y documentación."

# Claude Code ejecuta:
# 1. Renombra clase DataProcessor → OrderCalculator
# 2. Renombra archivo data_processor.py → order_calculator.py
# 3. Actualiza 8 imports en otros archivos
# 4. Actualiza 3 references en tests
# 5. Actualiza 2 mentions en README.md

Avanzado — Rename con discriminación semántica:

# Problema: "process" aparece en 50 lugares, pero solo
# 12 son la función que quieres renombrar

# Prompt:
> "Renombra la función process() de la clase OrderService
   en src/services/order_service.py a calculate_total().
   SOLO renombra las references a ESTA función específica,
   no a otras funciones llamadas 'process' en otros módulos."

# Claude Code entiende el contexto y solo cambia las
# references que apuntan a OrderService.process(),
# dejando intactas las demás funciones 'process'

Extract Refactoring

Cuándo extraer

Extraes cuando una función hace demasiado, cuando lógica se repite en varios lugares, o cuando un bloque de código tiene una responsabilidad clara que merece su propia función/clase.

Señales de que necesitas extract

# Señal 1: Función de 100+ líneas
def create_order(request):
    # 30 líneas de validación
    # 20 líneas de cálculo de precios
    # 15 líneas de procesamiento de cupones
    # 20 líneas de cargo a tarjeta
    # 15 líneas de notificaciones
    pass  # Total: 100 líneas, 5 responsabilidades

# Señal 2: Código duplicado
# En order_service.py:
tax = subtotal * 0.16
if region == "EU":
    tax = subtotal * 0.21
# En invoice_service.py (idéntico):
tax = subtotal * 0.16
if region == "EU":
    tax = subtotal * 0.21

# Señal 3: Comentario que describe un bloque
def process_payment(order):
    # --- Validate payment method ---
    if not order.payment_method:
        raise ValueError("No payment method")
    if order.payment_method.expired:
        raise ValueError("Payment method expired")
    # (Si necesitas un comentario para separar, es candidato a extract)

Extract function con Claude Code

Paso 1: Identificar qué extraer

# Prompt:
> "Analiza la función create_order() en src/services/order_service.py.
   ¿Qué bloques de lógica tienen responsabilidad propia y son
   candidatos a ser extraídos como funciones separadas?"

# Claude Code identifica:
# 1. Validación del request (líneas 15-45)
# 2. Cálculo de precios (líneas 47-67)
# 3. Procesamiento de cupones (líneas 69-83)
# 4. Cargo a tarjeta (líneas 85-104)
# 5. Envío de notificaciones (líneas 106-120)

Paso 2: Tests de regresión

> "Antes de refactorizar, escribe tests que capturen el
   comportamiento actual de create_order(). Incluye:
   - Happy path (orden creada exitosamente)
   - Validación fallida (datos faltantes)
   - Cupón inválido
   - Pago rechazado"

Paso 3: Extract

# Prompt:
> "Extrae la lógica de cálculo de precios (líneas 47-67)
   de create_order() a una nueva función calculate_order_total()
   en el mismo archivo. La nueva función debe recibir los items
   y el cupón, y retornar el total calculado. Actualiza
   create_order() para llamar a calculate_order_total()."

# Antes:
def create_order(request):
    # ... validación ...
    subtotal = sum(item.price * item.quantity for item in items)
    if coupon:
        discount = subtotal * coupon.discount_percent / 100
        subtotal -= discount
    tax = subtotal * TAX_RATE
    total = subtotal + tax + shipping
    # ... cargo a tarjeta ...

# Después:
def calculate_order_total(items, coupon=None, shipping=0):
    """Calcula el total de una orden con descuento y tax."""
    subtotal = sum(item.price * item.quantity for item in items)
    if coupon:
        discount = subtotal * coupon.discount_percent / 100
        subtotal -= discount
    tax = subtotal * TAX_RATE
    return subtotal + tax + shipping

def create_order(request):
    # ... validación ...
    total = calculate_order_total(items, coupon, shipping)
    # ... cargo a tarjeta ...

Paso 4: Verificar

> "Ejecuta los tests de create_order y confirma que
   todos pasan después del extract"

Extract class con Claude Code

Cuando la lógica extraída merece su propio módulo:

# Prompt:
> "La lógica de cálculo de precios en order_service.py
   es compleja y se usa también en invoice_service.py.
   Extrae toda la lógica de pricing a una nueva clase
   PricingEngine en src/services/pricing_engine.py.
   
   La clase debe tener métodos:
   - calculate_subtotal(items)
   - apply_discount(subtotal, coupon)
   - calculate_tax(amount, region)
   - calculate_total(items, coupon, region, shipping)
   
   Actualiza order_service.py e invoice_service.py para
   usar PricingEngine en lugar de código duplicado."

# Claude Code:
# 1. Crea src/services/pricing_engine.py con la clase
# 2. Mueve la lógica de pricing de order_service.py
# 3. Mueve la lógica duplicada de invoice_service.py
# 4. Agrega imports de PricingEngine en ambos services
# 5. Actualiza ambos services para usar PricingEngine

Comparación: Refactoring Manual vs Claude Code

CriterioManual / IDEClaude Code
Rename simpleIDE lo hace bienEquivalente
Rename en YAML/docsManualAutomático
Extract functionIDE sugiereClaude Code entiende contexto
Extract class cross-fileManual, tediosoCoordinado automáticamente
Actualizar testsManualAutomático
Discriminación semánticaDifícilNatural (entiende contexto)
VerificaciónEjecutar tests manualmenteClaude Code ejecuta y reporta

El valor de Claude Code: no es que haga cosas imposibles — es que coordina cambios en N archivos que manualmente tomarían 10x más tiempo y son propensos a errores.


Conexión con Proyecto

En el Proyecto del Módulo (cápsula 06) vas a ejecutar refactoring coordinado en un codebase real. Las técnicas de rename y extract de esta cápsula son los building blocks:

  • Rename para corregir naming inconsistente
  • Extract function para separar responsabilidades en funciones largas
  • Extract class para eliminar duplicación creando abstracciones compartidas

Troubleshooting

Problema 1: Claude Code renombra references que no debería

Causa: Hay otra función/variable con el mismo nombre en otro módulo.

Solución: Sé específico sobre cuál renombrar:

> "Renombra SOLO la función process() de OrderService
   en src/services/order_service.py. NO renombres
   process() en payment_service.py ni en utils.py"

Problema 2: Tests fallan después del extract

Causa: La función extraída tiene un bug o le faltan parámetros.

Solución: Compara el comportamiento antes/después:

> "Los tests fallan después del extract. Compara la
   función original con la extraída y encuentra qué
   cambió en el comportamiento."

Problema 3: Import circular después del extract

Causa: El nuevo módulo importa algo que a su vez importa el módulo original.

Solución: Reestructura los imports o mueve la dependencia:

> "El extract de PricingEngine causó un import circular
   entre pricing_engine.py y order_service.py. ¿Cómo
   puedo reestructurar para eliminarlo?"

Problema 4: No sé qué extraer primero

Causa: La función tiene muchos bloques candidatos.

Solución: Extrae uno a la vez, de adentro hacia afuera:

# Primero los bloques más internos (helper functions)
# Después los bloques más externos (service functions)
# Nunca extraigas 3 cosas a la vez

Problema 5: El rename rompe la configuración

Causa: Referencias en archivos YAML, JSON, o .env que Claude Code no detectó.

Solución: Pide búsqueda exhaustiva:

> "Busca la string 'user_handler' en TODOS los archivos
   del proyecto, incluyendo YAML, JSON, .env, Dockerfiles,
   y archivos de configuración de CI/CD"

Ejercicios

Ejercicio 1: Identificar candidatos a rename (Fácil)

Mira estos nombres de funciones y propón nombres mejores:

def do_stuff(data):      # En user_service.py, valida y guarda usuarios
def run(config):          # En email_sender.py, envía un email
def handle(event):        # En payment_processor.py, procesa un pago
def get(id):              # En product_repo.py, busca producto por ID
Ver solución
def validate_and_save_user(data):    # Describe qué hace
def send_email(config):              # Verbo específico
def process_payment(event):          # Dominio + acción
def get_product_by_id(id):           # Entidad + criterio

Regla: un buen nombre responde "¿qué hace esta función?" sin leer el código. Verbo + sustantivo + contexto.

Ejercicio 2: Planificar un rename cross-file (Fácil)

Tu proyecto tiene una clase DataManager que solo maneja usuarios. Está importada en 8 archivos. Escribe los 4 pasos del ciclo de rename con los prompts exactos que darías a Claude Code.

Ver solución
# Paso 1: Verificar tests
> "Ejecuta todos los tests y confirma que pasan"

# Paso 2: Encontrar references
> "Encuentra todas las references a DataManager en el
   proyecto: imports, uso directo, strings, docs, YAML,
   comentarios. Lista cada archivo y línea."

# Paso 3: Ejecutar rename
> "Renombra DataManager a UserService en todo el proyecto.
   Renombra también el archivo data_manager.py a
   user_service.py. Actualiza todos los imports, references,
   tests, docs, y configuración."

# Paso 4: Verificar
> "Ejecuta todos los tests y confirma que pasan después
   del rename."

Ejercicio 3: Identificar candidatos a extract (Medio)

Analiza esta función y propón qué extraer:

def process_order(order_data):
    # Validate
    if not order_data.get("items"):
        raise ValueError("No items")
    if not order_data.get("user_id"):
        raise ValueError("No user")
    for item in order_data["items"]:
        if item["quantity"] < 1:
            raise ValueError(f"Invalid quantity: {item['quantity']}")
    
    # Calculate
    subtotal = sum(i["price"] * i["quantity"] for i in order_data["items"])
    tax = subtotal * 0.16
    shipping = 9.99 if subtotal < 50 else 0
    total = subtotal + tax + shipping
    
    # Save
    order = Order(user_id=order_data["user_id"], total=total)
    db.session.add(order)
    for item in order_data["items"]:
        order_item = OrderItem(order_id=order.id, **item)
        db.session.add(order_item)
    db.session.commit()
    
    # Notify
    send_email(order_data["user_id"], f"Order {order.id} confirmed")
    publish_event("order_created", {"order_id": order.id})
    
    return order
Ver solución

4 candidatos a extract:

# Extract 1: Validación
def validate_order_data(order_data: dict) -> None:
    """Valida que los datos de la orden sean correctos."""
    if not order_data.get("items"):
        raise ValueError("No items")
    if not order_data.get("user_id"):
        raise ValueError("No user")
    for item in order_data["items"]:
        if item["quantity"] < 1:
            raise ValueError(f"Invalid quantity: {item['quantity']}")

# Extract 2: Cálculo
def calculate_order_totals(items: list) -> dict:
    """Calcula subtotal, tax, shipping, y total."""
    subtotal = sum(i["price"] * i["quantity"] for i in items)
    tax = subtotal * 0.16
    shipping = 9.99 if subtotal < 50 else 0
    return {"subtotal": subtotal, "tax": tax, "shipping": shipping, "total": subtotal + tax + shipping}

# Extract 3: Persistencia
def save_order(user_id: int, total: float, items: list) -> Order:
    """Guarda la orden y sus items en la DB."""
    order = Order(user_id=user_id, total=total)
    db.session.add(order)
    for item in items:
        db.session.add(OrderItem(order_id=order.id, **item))
    db.session.commit()
    return order

# Extract 4: Notificación
def notify_order_created(user_id: int, order_id: int) -> None:
    """Envía email y publica evento de orden creada."""
    send_email(user_id, f"Order {order_id} confirmed")
    publish_event("order_created", {"order_id": order_id})

Resultado: process_order() pasa de 25 líneas con 4 responsabilidades a 5 líneas que llaman 4 funciones especializadas.

Ejercicio 4: Extract con Claude Code (Medio)

Escribe el prompt completo para Claude Code que extraiga la lógica de cálculo del ejercicio anterior, incluyendo: crear la nueva función, actualizar process_order(), y escribir tests para la función extraída.

Ver solución
> "En src/services/order_service.py, extrae la lógica de
   cálculo de precios (líneas que calculan subtotal, tax,
   shipping, y total) de process_order() a una nueva función
   calculate_order_totals(items).
   
   La nueva función debe:
   1. Recibir una lista de items (cada uno con price y quantity)
   2. Retornar un dict con subtotal, tax, shipping, y total
   3. Estar en el mismo archivo, antes de process_order()
   
   Actualiza process_order() para llamar a
   calculate_order_totals() en lugar del código inline.
   
   Escribe tests unitarios para calculate_order_totals()
   en tests/test_order_service.py que cubran:
   - Un item simple
   - Múltiples items
   - Orden con shipping gratis (subtotal >= 50)
   - Orden con shipping (subtotal < 50)
   
   Después de hacer los cambios, ejecuta todos los tests."

Ejercicio 5: Rename + Extract combinado (Difícil)

Tienes una clase Helper en src/utils/helper.py con 30 métodos estáticos para todo (emails, cálculos, validaciones, formatting). Diseña un plan de refactoring en 3 fases usando rename y extract.

Ver solución
# Fase 1: Extract por dominio (sin rename)
> "Analiza los 30 métodos de Helper en src/utils/helper.py
   y agrúpalos por dominio: email, cálculos, validaciones,
   formatting. Lista cada método con su grupo."

# Resultado: 4 grupos de 6-8 métodos cada uno

# Fase 2: Extract classes
> "Extrae los métodos del grupo 'email' de Helper a una
   nueva clase EmailUtils en src/utils/email_utils.py.
   Actualiza todos los callers de Helper.send_email()
   a EmailUtils.send_email(). Ejecuta tests."

# Repetir para cada grupo:
# Helper.calculate_* → CalculationUtils
# Helper.validate_* → ValidationUtils
# Helper.format_* → FormattingUtils

# Fase 3: Rename para claridad
> "Renombra los métodos de EmailUtils para que sean
   más descriptivos: send() → send_email(),
   validate() → validate_email_format(),
   parse() → parse_email_address(). Actualiza references."

# Resultado final: 1 god class → 4 clases especializadas
# con nombres claros y responsabilidad única

Clave: no intentes hacer todo en un paso. Fase 1 agrupa, Fase 2 separa, Fase 3 limpia. Tests en cada paso.


Resumen

En esta cápsula aprendiste:

  • Rename refactoring cambia nombres y actualiza todas las references — Claude Code maneja YAML, docs, y configuración además de código
  • Extract refactoring separa lógica en funciones/clases nuevas — elimina duplicación y reduce responsabilidades
  • El ciclo es: tests → refactor → verify — siempre, sin excepciones
  • Claude Code discrimina semánticamente: renombra solo las references correctas, no coincidencias de texto
  • Extract progresivo: primero identifica qué extraer, luego extrae uno a la vez
  • La coordinación multi-file es el valor principal — Claude Code actualiza 10+ archivos coherentemente

Próxima cápsula: Move Module y Actualización de Imports. Vas a aprender a reorganizar la estructura de archivos sin romper imports.


Recursos Adicionales

  1. Refactoring Guru - Extract Method - Explicación visual del extract method con antes/después
  2. Refactoring Guru - Rename Method - Explicación visual del rename method
  3. Martin Fowler - Refactoring Catalog - Catálogo completo de refactorings con ejemplos
  4. Python - Naming Conventions (PEP 8) - Convenciones de naming para Python
  5. Working Effectively with Legacy Code - Extract and Override - Técnicas de extract para código sin tests
  6. rope - Python Refactoring Library - Librería Python de refactoring programático