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
| Criterio | Manual / IDE | Claude Code |
|---|---|---|
| Rename simple | IDE lo hace bien | Equivalente |
| Rename en YAML/docs | Manual | Automático |
| Extract function | IDE sugiere | Claude Code entiende contexto |
| Extract class cross-file | Manual, tedioso | Coordinado automáticamente |
| Actualizar tests | Manual | Automático |
| Discriminación semántica | Difícil | Natural (entiende contexto) |
| Verificación | Ejecutar tests manualmente | Claude 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
- Refactoring Guru - Extract Method - Explicación visual del extract method con antes/después
- Refactoring Guru - Rename Method - Explicación visual del rename method
- Martin Fowler - Refactoring Catalog - Catálogo completo de refactorings con ejemplos
- Python - Naming Conventions (PEP 8) - Convenciones de naming para Python
- Working Effectively with Legacy Code - Extract and Override - Técnicas de extract para código sin tests
- rope - Python Refactoring Library - Librería Python de refactoring programático