Módulo 3: Entender Arquitectura Existente
Pattern Identification y Anti-Pattern Detection
Pattern Identification y Anti-Pattern Detection
Descripción de la cápsula
Hasta ahora sabes mapear dependencias (cápsula 02) y trazar flujos (cápsula 03). Te falta el tercer pilar del architecture analysis: reconocer los patrones que el codebase usa — y los anti-patterns que acumula. Un patrón es una solución probada que se repite: MVC, repository, service layer. Un anti-pattern es una solución que crea más problemas de los que resuelve: god objects, circular dependencies, leaky abstractions.
En esta cápsula vas a usar Claude Code para identificar ambos. Lo importante es entender que esto NO es una clase de software architecture — no vas a diseñar la arquitectura ideal. Vas a analizar la arquitectura que ya existe, nombrar lo que encuentras, y decidir qué es oportunidad de mejora para el refactoring que viene en Phase 2.
La conexión con el proyecto es directa: en el Architecture Map (cápsula 05), la sección de pattern analysis es el componente que conecta observación con acción. Los dependency maps y flows te dicen qué hay. Los patterns y anti-patterns te dicen qué mejorar.
Patterns Arquitecturales Comunes
Qué son y por qué reconocerlos
Un pattern arquitectural es una estructura de organización que resuelve un problema recurrente. Reconocerlos en un codebase te da vocabulario para describir lo que ves y predicciones sobre dónde encontrar cosas.
Los 6 patterns más comunes en codebases Python
1. MVC / MTV (Model-View-Controller / Model-Template-View)
# Estructura típica:
# src/
# models/ ← Model: datos y lógica de negocio
# views/ ← View/Controller: manejo de requests
# templates/ ← Template: presentación (HTML)
# urls.py ← Routing
# Cómo reconocerlo con Claude Code:
> "Usa Explore para determinar si este proyecto sigue
el patrón MVC o MTV. ¿Hay separación clara entre
modelos, vistas/controllers, y templates?"
2. Service Layer
# Estructura típica:
# src/
# api/routes/ ← Endpoints (delgados)
# services/ ← Lógica de negocio (gruesos)
# models/ ← Datos
# repositories/ ← Acceso a DB
# Cómo reconocerlo:
> "Usa Explore para verificar si la lógica de negocio
está centralizada en un directorio de services o
dispersa en los route handlers"
# Señal de service layer:
# - Route handlers solo llaman a un service y retornan
# - Services contienen toda la lógica
# - Route handlers tienen <20 líneas
3. Repository Pattern
# Estructura típica:
# src/
# repositories/
# user_repo.py ← Queries de users
# order_repo.py ← Queries de orders
# services/
# user_service.py ← Usa user_repo, no hace SQL
# Cómo reconocerlo:
> "Usa Explore para determinar si el acceso a base de datos
está centralizado en archivos/clases dedicadas (repositories)
o disperso en los services"
# Señal de repository:
# - Services nunca importan SQLAlchemy/ORM directamente
# - Toda interacción con DB pasa por un repo
4. Event-Driven / Pub-Sub
# Estructura típica:
# src/
# events/
# event_bus.py ← Dispatcher de eventos
# handlers/ ← Subscribers
# services/
# order_service.py ← Publica OrderCreated
# Cómo reconocerlo:
> "Usa Explore para buscar si el proyecto usa un sistema
de eventos o pub/sub. Busca event bus, signal handlers,
listeners, o publish/subscribe patterns"
5. Middleware Pipeline
# Estructura típica:
# src/
# middleware/
# auth.py ← Verificar JWT
# cors.py ← Cross-origin
# logging.py ← Request logging
# rate_limit.py ← Rate limiting
# Cómo reconocerlo:
> "Usa Explore para listar todos los middleware del proyecto.
¿En qué orden se ejecutan? ¿Qué hace cada uno?"
6. Factory Pattern
# Estructura típica:
# Una función/clase que crea objetos basándose en parámetros
# src/factories/notification_factory.py
def create_notification(channel: str, message: str):
if channel == "email":
return EmailNotification(message)
elif channel == "sms":
return SMSNotification(message)
elif channel == "push":
return PushNotification(message)
# Cómo reconocerlo:
> "Usa Explore para buscar funciones que crean diferentes
tipos de objetos basándose en un parámetro. Busca
factory functions o factory classes"
Prompt genérico para identificar patterns
> "Usa Explore para analizar la arquitectura de este proyecto
e identificar qué patterns arquitecturales usa. Para cada
pattern que encuentres, indica:
1. Nombre del pattern
2. Dónde se implementa (archivos/directorios)
3. Si se implementa de forma consistente o parcial
4. Ejemplo concreto de uso"
Anti-Patterns: Oportunidades de Mejora
La mentalidad correcta
Los anti-patterns no son errores del desarrollador original. Son consecuencias naturales de la evolución del código: features se agregan bajo presión, el diseño original no anticipó ciertos cambios, y las convenciones del equipo cambiaron con el tiempo.
Detectar anti-patterns no es criticar — es encontrar oportunidades de refactoring con el mayor impacto.
Los 7 anti-patterns más comunes
1. God Object / God Class
# Un objeto que sabe y hace demasiado:
class ApplicationManager:
def handle_auth(self): ...
def process_payment(self): ...
def send_email(self): ...
def generate_report(self): ...
def manage_inventory(self): ...
def validate_input(self): ...
# 2000+ líneas, 40+ métodos
# Cómo detectarlo con Claude Code:
> "Usa Explore para encontrar clases con más de 500 líneas
o más de 15 métodos. ¿Alguna tiene responsabilidades
de múltiples dominios?"
Impacto: Todo cambio afecta esta clase. Tests son enormes. Merge conflicts constantes.
2. Circular Dependencies
# Módulo A importa B, B importa A:
# user_service.py:
from order_service import OrderService # →
# order_service.py:
from user_service import UserService # ←
# Cómo detectarlo:
> "Usa Explore para buscar dependencias circulares en el
proyecto. ¿Hay módulos que se importan mutuamente?"
Impacto: Import errors en runtime, difícil de testear en aislamiento, indica acoplamiento alto.
3. Shotgun Surgery
# Un cambio requiere modificar muchos archivos:
# Cambiar el formato de "user_id" requiere editar:
# - models/user.py
# - services/user_service.py
# - services/order_service.py
# - api/routes/users.py
# - api/routes/orders.py
# - utils/auth.py
# - tests/test_user.py (x3)
# - config/settings.py
# Cómo detectarlo:
> "Usa Explore para analizar: si necesito cambiar cómo
se representa el user_id (de int a UUID), ¿cuántos
archivos necesito modificar? ¿El concepto de user_id
está centralizado o disperso?"
Impacto: Cambios simples requieren tocar muchos archivos. Alto riesgo de olvidar uno.
4. Feature Envy
# Una función usa más datos de otro objeto que del suyo:
class OrderService:
def calculate_discount(self, user):
# Esta función accede a 5 atributos de user
# y ninguno de OrderService
if user.tier == "premium":
if user.orders_count > 10:
if user.registration_date < one_year_ago:
return user.loyalty_points * 0.01
return 0
# Debería estar en UserService o User, no en OrderService
# Cómo detectarlo:
> "Usa Explore para buscar funciones que acceden
extensivamente a atributos de objetos que reciben
como parámetro, más que a sus propios atributos"
Impacto: Lógica en el lugar equivocado. Difícil de encontrar. Duplicación cuando otro servicio necesita la misma lógica.
5. Spaghetti Code
# Flujo de control que es difícil de seguir:
def process_order(data):
if data.get("type") == "subscription":
if data.get("existing_user"):
user = get_user(data["user_id"])
if user.is_active:
if user.payment_method:
# ... 5 niveles más de anidación
else:
if data.get("trial"):
# ... más anidación
elif data.get("type") == "one_time":
# ... otra cadena de if/else
# 200+ líneas de if/elif/else anidado
# Cómo detectarlo:
> "Usa Explore para encontrar funciones con anidación
profunda (4+ niveles de if/for/while) o funciones
de más de 100 líneas con lógica condicional compleja"
Impacto: Imposible de entender sin trazar manualmente cada path. Tests requieren cobertura combinatoria.
6. Leaky Abstraction
# La abstracción expone detalles de implementación:
class UserRepository:
def get_user(self, user_id: int):
# Se supone que abstrae la DB, pero...
query = "SELECT * FROM users WHERE id = %s"
result = self.connection.execute(query, (user_id,))
return dict(result) # retorna dict, no User object
def get_active_users(self):
# Retorna cursor de SQLAlchemy directamente
return self.session.query(User).filter(User.active == True)
# El caller necesita saber de SQLAlchemy para usar esto
# Cómo detectarlo:
> "Usa Explore para buscar abstracciones que exponen
detalles internos: repositories que retornan objetos
del ORM, services que exponen excepciones de la DB,
o APIs que exponen estructura interna de datos"
Impacto: Cambiar la implementación interna rompe a los callers. La abstracción no cumple su propósito.
7. Dead Code
# Funciones, imports, o variables que nunca se usan:
import os # nunca se usa
from datetime import timedelta # nunca se usa
def old_calculate_tax(amount): # nadie llama esta función
"""Deprecated: usar calculate_tax_v2"""
return amount * 0.16
LEGACY_API_URL = "https://old-api.example.com" # no se referencia
# Cómo detectarlo:
> "Usa Explore para encontrar funciones definidas pero
nunca llamadas, imports no utilizados, y constantes
no referenciadas en el proyecto"
Impacto: Confunde a nuevos developers. Aumenta la superficie del código sin valor. Puede ser un security risk (código con vulnerabilidades que "ya no se usa" pero sigue accesible).
Prompt Maestro para Anti-Pattern Detection
> "Usa Explore para analizar este proyecto y encontrar
anti-patterns. Busca específicamente:
1. God objects (clases con 500+ líneas o 15+ métodos)
2. Circular dependencies (imports mutuos)
3. Shotgun surgery (conceptos dispersos en muchos archivos)
4. Deep nesting (funciones con 4+ niveles de indentación)
5. Dead code (funciones, imports, o variables no usadas)
6. Leaky abstractions (detalles de implementación expuestos)
Para cada anti-pattern encontrado, reporta:
- Archivo y línea
- Severidad (alta/media/baja)
- Impacto si no se corrige
- Sugerencia de refactoring"
De Anti-Patterns a Decisiones de Refactoring
El framework de priorización
No todos los anti-patterns merecen ser corregidos. Usa esta matriz:
| Alto impacto | Bajo impacto | |
|---|---|---|
| Bajo riesgo | ✅ Hacer primero | 🔄 Hacer cuando sea conveniente |
| Alto riesgo | ⚠️ Planificar con cuidado | ❌ Probablemente no vale la pena |
Alto impacto + bajo riesgo: Dead code removal, imports cleanup, rename inconsistencies Alto impacto + alto riesgo: Separar god objects, resolver circular dependencies Bajo impacto + bajo riesgo: Modernizar syntax, agregar type hints Bajo impacto + alto riesgo: Cambiar patrones que funcionan por "mejores" patrones
Conectando con el Módulo 4
Cada anti-pattern que encuentras aquí es un candidato para refactoring en el Módulo 4. El Architecture Map que construyes en la cápsula 05 incluye esta lista priorizada de anti-patterns — es el backlog de mejoras que informará tus decisiones de refactoring.
Comparación: Análisis Manual vs Claude Code
| Criterio | Manual | Claude Code |
|---|---|---|
| Encontrar god objects | Revisar cada archivo manualmente | Prompt: "clases con 500+ líneas" |
| Circular dependencies | Trazar imports archivo por archivo | Prompt: "imports mutuos" |
| Dead code | Buscar cada función y verificar callers | Prompt: "funciones no llamadas" |
| Tiempo para un módulo | 2-4 horas | 15-30 minutos |
| Consistencia | Depende de experiencia | Consistente con cada prompt |
| False positives | Menos (experiencia humana) | Más (requiere verificación) |
El rol de Claude Code: encuentra candidatos rápido. Tu rol: verificar, priorizar, y decidir qué hacer.
Conexión con Proyecto
En el Architecture Map (cápsula 05), la sección de pattern analysis tiene dos partes:
- Patterns identificados: qué patterns usa el codebase, dónde, y si son consistentes
- Anti-patterns encontrados: lista priorizada con severidad, impacto, y sugerencia de refactoring
Este es el componente más accionable del Architecture Map — traduce observación en decisiones de mejora.
Troubleshooting
Problema 1: Claude Code reporta anti-patterns que no son
Causa: Algunos patrones que parecen anti-patterns tienen razones válidas.
Solución: Verifica cada finding. Un god object puede ser intencional (facade pattern). Una función de 150 líneas puede ser un state machine que se beneficia de estar en un solo lugar.
Problema 2: Demasiados anti-patterns encontrados
Causa: Codebases legacy tienen acumulación natural de tech debt.
Solución: Prioriza con la matriz impacto/riesgo. No intentes arreglar todo — los 3-5 de mayor impacto y menor riesgo son suficientes para empezar.
Problema 3: No reconozco el pattern que usa el codebase
Causa: No todos los codebases usan patterns nombrados. Algunos tienen arquitectura orgánica.
Solución: Describe lo que ves sin forzar un nombre:
> "No intentes clasificar en un pattern conocido.
Solo describe cómo está organizado el código:
¿dónde está la lógica? ¿dónde están los datos?
¿cómo se comunican las partes?"
Problema 4: El equipo no está de acuerdo con mis findings
Causa: Los anti-patterns pueden ser decisiones intencionales.
Solución: Presenta findings como preguntas, no como juicios: "¿La clase ApplicationManager de 2000 líneas es intencional o es candidata a separación?" en vez de "ApplicationManager es un god object que debe separarse."
Ejercicios
Ejercicio 1: Clasificar patterns (Fácil)
Para cada descripción, identifica el pattern:
- Los route handlers solo tienen 5 líneas y delegan toda la lógica a clases en
services/ - Todo acceso a la DB pasa por clases en
repositories/— los services nunca hacen SQL - Cuando se crea una orden, se publica un evento que 4 handlers diferentes procesan
- Hay una función que recibe un string "email"/"sms"/"push" y retorna el notificador correcto
Ver solución
- Service Layer — lógica en services, controllers delgados
- Repository Pattern — acceso a datos centralizado
- Event-Driven / Pub-Sub — comunicación por eventos
- Factory Pattern — creación de objetos por parámetro
Ejercicio 2: Detectar anti-patterns (Fácil)
Para cada fragmento de código, identifica el anti-pattern:
# Fragmento A:
class AppManager:
def authenticate_user(self): ...
def create_order(self): ...
def send_email(self): ...
def generate_pdf(self): ...
def calculate_tax(self): ...
def update_inventory(self): ...
# 30 métodos más...
# Fragmento B:
# auth_service.py
from user_service import UserService
# user_service.py
from auth_service import AuthService
# Fragmento C:
def process(data):
if data["type"] == "A":
if data["status"] == "active":
if data["role"] == "admin":
if data["region"] == "US":
# ... hacer algo
Ver solución
- Fragmento A: God Object — una clase con responsabilidades de 6+ dominios diferentes
- Fragmento B: Circular Dependency — dos módulos se importan mutuamente
- Fragmento C: Spaghetti Code — anidación profunda (4 niveles de if)
Ejercicio 3: Escribir prompts de detección (Medio)
Escribe un prompt específico para Claude Code que detecte cada anti-pattern en un proyecto real:
Ver solución
# God Objects:
> "Usa Explore para encontrar las 5 clases más grandes
del proyecto (por número de métodos y líneas).
¿Alguna tiene responsabilidades de múltiples dominios?"
# Circular Dependencies:
> "Usa Explore para buscar pares de módulos que se
importan mutuamente. Lista cada par con los imports
específicos que causan la circularidad."
# Shotgun Surgery:
> "Si necesitara agregar un nuevo campo 'phone' al
modelo de usuario, ¿cuántos archivos tendría que
modificar? Lista cada archivo y qué cambiaría."
# Dead Code:
> "Usa Explore para encontrar: a) funciones definidas
pero nunca llamadas, b) imports no utilizados,
c) variables asignadas pero nunca leídas."
# Spaghetti Code:
> "Usa Explore para encontrar las 5 funciones con mayor
complejidad ciclomática (más branches if/elif/else,
loops anidados, o try/except anidados)."
Ejercicio 4: Priorizar anti-patterns (Medio)
Dado estos findings en un proyecto, priorízalos usando la matriz impacto/riesgo:
- UserManager tiene 1500 líneas y 35 métodos
- 12 imports no utilizados dispersos en el proyecto
- auth_service y user_service tienen dependencia circular
- 3 funciones de 200+ líneas con deep nesting
- 5 funciones que nunca se llaman (dead code)
Ver solución
| Finding | Impacto | Riesgo | Prioridad | Razón |
|---|---|---|---|---|
| Imports no usados | Bajo | Bajo | 🔄 Conveniente | No afecta funcionalidad, fácil de limpiar |
| Dead code (5 funciones) | Medio | Bajo | ✅ Hacer primero | Reduce confusión, 0 riesgo de romper algo |
| Circular dependency | Alto | Medio | ⚠️ Planificar | Afecta testability, requiere reestructurar |
| UserManager god object | Alto | Alto | ⚠️ Planificar con cuidado | Máximo impacto pero riesgoso de separar |
| Deep nesting (3 funciones) | Medio | Medio | ✅ Hacer segundo | Mejora legibilidad, riesgo moderado |
Orden sugerido: Dead code → Deep nesting → Imports → Circular dep → God object
Ejercicio 5: Architecture analysis completo (Difícil)
Para un proyecto que conozcas (o un open-source), ejecuta el prompt maestro de anti-pattern detection y produce un reporte con: findings, severidad, impacto, y plan de acción priorizado.
Ver solución
Usa el prompt maestro de la sección anterior en un proyecto real. Tu reporte debe tener esta estructura:
# Anti-Pattern Analysis: [Proyecto]
## Findings
### 1. [Anti-pattern]: [Descripción]
- **Archivo:** [path:línea]
- **Severidad:** Alta/Media/Baja
- **Impacto:** [Qué problema causa]
- **Sugerencia:** [Qué refactoring aplicar]
### 2. [Siguiente finding...]
[...]
## Plan de Acción Priorizado
| # | Finding | Acción | Riesgo | Sprint |
|---|---------|--------|--------|--------|
| 1 | [Más urgente] | [Qué hacer] | Bajo | 1 |
| 2 | [Siguiente] | [Qué hacer] | Medio | 1 |
| 3 | [Puede esperar] | [Qué hacer] | Alto | 2 |
Resumen
En esta cápsula aprendiste:
- 6 patterns comunes en codebases Python: MVC, Service Layer, Repository, Event-Driven, Middleware Pipeline, Factory
- 7 anti-patterns que buscar: God Object, Circular Dependencies, Shotgun Surgery, Feature Envy, Spaghetti Code, Leaky Abstractions, Dead Code
- Claude Code detecta anti-patterns rápido con prompts específicos — lo que manualmente toma horas, con prompts toma minutos
- Anti-patterns son oportunidades, no críticas — todo código evoluciona y acumula tech debt naturalmente
- La priorización es clave: usa la matriz impacto/riesgo para decidir qué vale la pena corregir
- Esto conecta directamente con refactoring: cada anti-pattern es un candidato para el Módulo 4
Próxima cápsula: Proyecto del Módulo — Architecture Map de Proyecto Real. Vas a combinar dependency maps, flow analysis, y pattern identification en un documento completo que sirve como base para decisiones de refactoring.
Recursos Adicionales
- Refactoring: Improving the Design of Existing Code - Martin Fowler - La referencia definitiva sobre refactoring y code smells
- Design Patterns - Gang of Four - Los patterns originales explicados
- AntiPatterns - Brown et al. - Catálogo completo de anti-patterns de software
- Python Design Patterns - Patterns específicos para Python
- Code Smells - Refactoring Guru - Catálogo visual de code smells con refactorings sugeridos
- Cyclomatic Complexity - Radon - Herramienta Python para medir complejidad ciclomática