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 impactoBajo 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

CriterioManualClaude Code
Encontrar god objectsRevisar cada archivo manualmentePrompt: "clases con 500+ líneas"
Circular dependenciesTrazar imports archivo por archivoPrompt: "imports mutuos"
Dead codeBuscar cada función y verificar callersPrompt: "funciones no llamadas"
Tiempo para un módulo2-4 horas15-30 minutos
ConsistenciaDepende de experienciaConsistente con cada prompt
False positivesMenos (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:

  1. Patterns identificados: qué patterns usa el codebase, dónde, y si son consistentes
  2. 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:

  1. Los route handlers solo tienen 5 líneas y delegan toda la lógica a clases en services/
  2. Todo acceso a la DB pasa por clases en repositories/ — los services nunca hacen SQL
  3. Cuando se crea una orden, se publica un evento que 4 handlers diferentes procesan
  4. Hay una función que recibe un string "email"/"sms"/"push" y retorna el notificador correcto
Ver solución
  1. Service Layer — lógica en services, controllers delgados
  2. Repository Pattern — acceso a datos centralizado
  3. Event-Driven / Pub-Sub — comunicación por eventos
  4. 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:

  1. UserManager tiene 1500 líneas y 35 métodos
  2. 12 imports no utilizados dispersos en el proyecto
  3. auth_service y user_service tienen dependencia circular
  4. 3 funciones de 200+ líneas con deep nesting
  5. 5 funciones que nunca se llaman (dead code)
Ver solución
FindingImpactoRiesgoPrioridadRazón
Imports no usadosBajoBajo🔄 ConvenienteNo afecta funcionalidad, fácil de limpiar
Dead code (5 funciones)MedioBajo✅ Hacer primeroReduce confusión, 0 riesgo de romper algo
Circular dependencyAltoMedio⚠️ PlanificarAfecta testability, requiere reestructurar
UserManager god objectAltoAlto⚠️ Planificar con cuidadoMáximo impacto pero riesgoso de separar
Deep nesting (3 funciones)MedioMedio✅ Hacer segundoMejora 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

  1. Refactoring: Improving the Design of Existing Code - Martin Fowler - La referencia definitiva sobre refactoring y code smells
  2. Design Patterns - Gang of Four - Los patterns originales explicados
  3. AntiPatterns - Brown et al. - Catálogo completo de anti-patterns de software
  4. Python Design Patterns - Patterns específicos para Python
  5. Code Smells - Refactoring Guru - Catálogo visual de code smells con refactorings sugeridos
  6. Cyclomatic Complexity - Radon - Herramienta Python para medir complejidad ciclomática