Module 3: Understand an Existing Architecture
Pattern Identification and Anti-Pattern Detection
Pattern Identification and Anti-Pattern Detection
Capsule description
By now you know how to map dependencies (capsule 02) and trace flows (capsule 03). You're missing the third pillar of architecture analysis: recognizing the patterns the codebase uses — and the anti-patterns it accumulates. A pattern is a proven solution that repeats: MVC, repository, service layer. An anti-pattern is a solution that creates more problems than it solves: god objects, circular dependencies, leaky abstractions.
In this capsule you're going to use Claude Code to identify both. What matters is understanding that this is NOT a software architecture class — you're not going to design the ideal architecture. You're going to analyze the architecture that already exists, name what you find, and decide what's an improvement opportunity for the refactoring coming in Phase 2.
The connection with the project is direct: in the Architecture Map (capsule 05), the pattern analysis section is the component that connects observation with action. The dependency maps and flows tell you what's there. The patterns and anti-patterns tell you what to improve.
Common Architectural Patterns
What they are and why to recognize them
An architectural pattern is an organizing structure that solves a recurring problem. Recognizing them in a codebase gives you vocabulary to describe what you see and predictions about where to find things.
The 6 most common patterns in Python codebases
1. MVC / MTV (Model-View-Controller / Model-Template-View)
# Typical structure:
# src/
# models/ ← Model: data and business logic
# views/ ← View/Controller: request handling
# templates/ ← Template: presentation (HTML)
# urls.py ← Routing
# How to recognize it with Claude Code:
> "Use Explore to determine whether this project follows
the MVC or MTV pattern. Is there a clear separation between
models, views/controllers, and templates?"
2. Service Layer
# Typical structure:
# src/
# api/routes/ ← Endpoints (thin)
# services/ ← Business logic (thick)
# models/ ← Data
# repositories/ ← DB access
# How to recognize it:
> "Use Explore to verify whether the business logic
is centralized in a services directory or
scattered across the route handlers"
# Signal of a service layer:
# - Route handlers only call a service and return
# - Services contain all the logic
# - Route handlers have <20 lines
3. Repository Pattern
# Typical structure:
# src/
# repositories/
# user_repo.py ← User queries
# order_repo.py ← Order queries
# services/
# user_service.py ← Uses user_repo, doesn't do SQL
# How to recognize it:
> "Use Explore to determine whether database access
is centralized in dedicated files/classes (repositories)
or scattered across the services"
# Signal of a repository:
# - Services never import SQLAlchemy/ORM directly
# - All DB interaction goes through a repo
4. Event-Driven / Pub-Sub
# Typical structure:
# src/
# events/
# event_bus.py ← Event dispatcher
# handlers/ ← Subscribers
# services/
# order_service.py ← Publishes OrderCreated
# How to recognize it:
> "Use Explore to check whether the project uses an event
or pub/sub system. Look for an event bus, signal handlers,
listeners, or publish/subscribe patterns"
5. Middleware Pipeline
# Typical structure:
# src/
# middleware/
# auth.py ← Verify JWT
# cors.py ← Cross-origin
# logging.py ← Request logging
# rate_limit.py ← Rate limiting
# How to recognize it:
> "Use Explore to list all the project's middleware.
In what order do they run? What does each one do?"
6. Factory Pattern
# Typical structure:
# A function/class that creates objects based on parameters
# 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)
# How to recognize it:
> "Use Explore to look for functions that create different
types of objects based on a parameter. Look for
factory functions or factory classes"
Generic prompt to identify patterns
> "Use Explore to analyze this project's architecture
and identify what architectural patterns it uses. For each
pattern you find, state:
1. The name of the pattern
2. Where it's implemented (files/directories)
3. Whether it's implemented consistently or partially
4. A concrete example of use"
Anti-Patterns: Improvement Opportunities
The right mindset
Anti-patterns aren't mistakes by the original developer. They're natural consequences of the code's evolution: features get added under pressure, the original design didn't anticipate certain changes, and the team's conventions changed over time.
Detecting anti-patterns isn't criticizing — it's finding refactoring opportunities with the greatest impact.
The 7 most common anti-patterns
1. God Object / God Class
# An object that knows and does too much:
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+ lines, 40+ methods
# How to detect it with Claude Code:
> "Use Explore to find classes with more than 500 lines
or more than 15 methods. Does any have responsibilities
from multiple domains?"
Impact: Every change affects this class. Tests are enormous. Constant merge conflicts.
2. Circular Dependencies
# Module A imports B, B imports A:
# user_service.py:
from order_service import OrderService # →
# order_service.py:
from user_service import UserService # ←
# How to detect it:
> "Use Explore to look for circular dependencies in the
project. Are there modules that import each other?"
Impact: Import errors at runtime, hard to test in isolation, indicates high coupling.
3. Shotgun Surgery
# One change requires modifying many files:
# Changing the format of "user_id" requires editing:
# - 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
# How to detect it:
> "Use Explore to analyze: if I need to change how
the user_id is represented (from int to UUID), how many
files do I need to modify? Is the user_id concept
centralized or scattered?"
Impact: Simple changes require touching many files. High risk of forgetting one.
4. Feature Envy
# A function uses more data from another object than from its own:
class OrderService:
def calculate_discount(self, user):
# This function accesses 5 attributes of user
# and none of 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
# It should be in UserService or User, not in OrderService
# How to detect it:
> "Use Explore to look for functions that access
the attributes of objects they receive as a parameter
extensively, more than their own attributes"
Impact: Logic in the wrong place. Hard to find. Duplication when another service needs the same logic.
5. Spaghetti Code
# Control flow that's hard to follow:
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 more levels of nesting
else:
if data.get("trial"):
# ... more nesting
elif data.get("type") == "one_time":
# ... another if/else chain
# 200+ lines of nested if/elif/else
# How to detect it:
> "Use Explore to find functions with deep
nesting (4+ levels of if/for/while) or functions
over 100 lines with complex conditional logic"
Impact: Impossible to understand without manually tracing each path. Tests require combinatorial coverage.
6. Leaky Abstraction
# The abstraction exposes implementation details:
class UserRepository:
def get_user(self, user_id: int):
# It's supposed to abstract the DB, but...
query = "SELECT * FROM users WHERE id = %s"
result = self.connection.execute(query, (user_id,))
return dict(result) # returns a dict, not a User object
def get_active_users(self):
# Returns a SQLAlchemy cursor directly
return self.session.query(User).filter(User.active == True)
# The caller needs to know SQLAlchemy to use this
# How to detect it:
> "Use Explore to look for abstractions that expose
internal details: repositories that return ORM
objects, services that expose DB exceptions,
or APIs that expose the internal data structure"
Impact: Changing the internal implementation breaks the callers. The abstraction doesn't fulfill its purpose.
7. Dead Code
# Functions, imports, or variables that are never used:
import os # never used
from datetime import timedelta # never used
def old_calculate_tax(amount): # nobody calls this function
"""Deprecated: use calculate_tax_v2"""
return amount * 0.16
LEGACY_API_URL = "https://old-api.example.com" # not referenced
# How to detect it:
> "Use Explore to find functions that are defined but
never called, unused imports, and constants
not referenced in the project"
Impact: Confuses new developers. Increases the code surface without value. It can be a security risk (code with vulnerabilities that's "no longer used" but still accessible).
Master Prompt for Anti-Pattern Detection
> "Use Explore to analyze this project and find
anti-patterns. Look specifically for:
1. God objects (classes with 500+ lines or 15+ methods)
2. Circular dependencies (mutual imports)
3. Shotgun surgery (concepts scattered across many files)
4. Deep nesting (functions with 4+ levels of indentation)
5. Dead code (unused functions, imports, or variables)
6. Leaky abstractions (exposed implementation details)
For each anti-pattern found, report:
- File and line
- Severity (high/medium/low)
- Impact if not corrected
- Refactoring suggestion"
From Anti-Patterns to Refactoring Decisions
The prioritization framework
Not all anti-patterns deserve to be fixed. Use this matrix:
| High impact | Low impact | |
|---|---|---|
| Low risk | ✅ Do first | 🔄 Do when convenient |
| High risk | ⚠️ Plan carefully | ❌ Probably not worth it |
High impact + low risk: Dead code removal, imports cleanup, rename inconsistencies High impact + high risk: Splitting god objects, resolving circular dependencies Low impact + low risk: Modernizing syntax, adding type hints Low impact + high risk: Changing patterns that work for "better" patterns
Connecting with Module 4
Every anti-pattern you find here is a candidate for refactoring in Module 4. The Architecture Map you build in capsule 05 includes this prioritized list of anti-patterns — it's the improvement backlog that will inform your refactoring decisions.
Comparison: Manual Analysis vs Claude Code
| Criterion | Manual | Claude Code |
|---|---|---|
| Finding god objects | Review each file manually | Prompt: "classes with 500+ lines" |
| Circular dependencies | Trace imports file by file | Prompt: "mutual imports" |
| Dead code | Search each function and check callers | Prompt: "uncalled functions" |
| Time for a module | 2-4 hours | 15-30 minutes |
| Consistency | Depends on experience | Consistent with each prompt |
| False positives | Fewer (human experience) | More (requires verification) |
Claude Code's role: finds candidates fast. Your role: verify, prioritize, and decide what to do.
Connection with the Project
In the Architecture Map (capsule 05), the pattern analysis section has two parts:
- Identified patterns: what patterns the codebase uses, where, and whether they're consistent
- Anti-patterns found: a prioritized list with severity, impact, and refactoring suggestion
This is the most actionable component of the Architecture Map — it translates observation into improvement decisions.
Troubleshooting
Problem 1: Claude Code reports anti-patterns that aren't
Cause: Some patterns that look like anti-patterns have valid reasons.
Solution: Verify each finding. A god object may be intentional (facade pattern). A 150-line function may be a state machine that benefits from being in one place.
Problem 2: Too many anti-patterns found
Cause: Legacy codebases have a natural accumulation of tech debt.
Solution: Prioritize with the impact/risk matrix. Don't try to fix everything — the 3-5 with the highest impact and lowest risk are enough to start.
Problem 3: I don't recognize the pattern the codebase uses
Cause: Not all codebases use named patterns. Some have organic architecture.
Solution: Describe what you see without forcing a name:
> "Don't try to classify it into a known pattern.
Just describe how the code is organized:
where's the logic? where's the data?
how do the parts communicate?"
Problem 4: The team disagrees with my findings
Cause: Anti-patterns can be intentional decisions.
Solution: Present findings as questions, not as judgments: "Is the 2000-line ApplicationManager class intentional or a candidate for splitting?" instead of "ApplicationManager is a god object that must be split."
Exercises
Exercise 1: Classify patterns (Easy)
For each description, identify the pattern:
- The route handlers only have 5 lines and delegate all the logic to classes in
services/ - All DB access goes through classes in
repositories/— the services never do SQL - When an order is created, an event is published that 4 different handlers process
- There's a function that receives a string "email"/"sms"/"push" and returns the right notifier
See solution
- Service Layer — logic in services, thin controllers
- Repository Pattern — centralized data access
- Event-Driven / Pub-Sub — communication by events
- Factory Pattern — object creation by parameter
Exercise 2: Detect anti-patterns (Easy)
For each code fragment, identify the anti-pattern:
# Fragment 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 more methods...
# Fragment B:
# auth_service.py
from user_service import UserService
# user_service.py
from auth_service import AuthService
# Fragment C:
def process(data):
if data["type"] == "A":
if data["status"] == "active":
if data["role"] == "admin":
if data["region"] == "US":
# ... do something
See solution
- Fragment A: God Object — a class with responsibilities from 6+ different domains
- Fragment B: Circular Dependency — two modules import each other
- Fragment C: Spaghetti Code — deep nesting (4 levels of if)
Exercise 3: Write detection prompts (Medium)
Write a specific prompt for Claude Code that detects each anti-pattern in a real project:
See solution
# God Objects:
> "Use Explore to find the 5 largest classes
in the project (by number of methods and lines).
Does any have responsibilities from multiple domains?"
# Circular Dependencies:
> "Use Explore to look for pairs of modules that
import each other. List each pair with the
specific imports that cause the circularity."
# Shotgun Surgery:
> "If I needed to add a new 'phone' field to the
user model, how many files would I have to
modify? List each file and what would change."
# Dead Code:
> "Use Explore to find: a) functions that are defined
but never called, b) unused imports,
c) variables assigned but never read."
# Spaghetti Code:
> "Use Explore to find the 5 functions with the highest
cyclomatic complexity (most if/elif/else branches,
nested loops, or nested try/except)."
Exercise 4: Prioritize anti-patterns (Medium)
Given these findings in a project, prioritize them using the impact/risk matrix:
- UserManager has 1500 lines and 35 methods
- 12 unused imports scattered across the project
- auth_service and user_service have a circular dependency
- 3 functions of 200+ lines with deep nesting
- 5 functions that are never called (dead code)
See solution
| Finding | Impact | Risk | Priority | Reason |
|---|---|---|---|---|
| Unused imports | Low | Low | 🔄 Convenient | Doesn't affect functionality, easy to clean |
| Dead code (5 functions) | Medium | Low | ✅ Do first | Reduces confusion, 0 risk of breaking something |
| Circular dependency | High | Medium | ⚠️ Plan | Affects testability, requires restructuring |
| UserManager god object | High | High | ⚠️ Plan carefully | Maximum impact but risky to split |
| Deep nesting (3 functions) | Medium | Medium | ✅ Do second | Improves readability, moderate risk |
Suggested order: Dead code → Deep nesting → Imports → Circular dep → God object
Exercise 5: Complete architecture analysis (Hard)
For a project you know (or an open-source one), run the master anti-pattern detection prompt and produce a report with: findings, severity, impact, and a prioritized action plan.
See solution
Use the master prompt from the previous section on a real project. Your report should have this structure:
# Anti-Pattern Analysis: [Project]
## Findings
### 1. [Anti-pattern]: [Description]
- **File:** [path:line]
- **Severity:** High/Medium/Low
- **Impact:** [What problem it causes]
- **Suggestion:** [What refactoring to apply]
### 2. [Next finding...]
[...]
## Prioritized Action Plan
| # | Finding | Action | Risk | Sprint |
|---|---------|--------|--------|--------|
| 1 | [Most urgent] | [What to do] | Low | 1 |
| 2 | [Next] | [What to do] | Medium | 1 |
| 3 | [Can wait] | [What to do] | High | 2 |
Summary
In this capsule you learned:
- 6 common patterns in Python codebases: MVC, Service Layer, Repository, Event-Driven, Middleware Pipeline, Factory
- 7 anti-patterns to look for: God Object, Circular Dependencies, Shotgun Surgery, Feature Envy, Spaghetti Code, Leaky Abstractions, Dead Code
- Claude Code detects anti-patterns fast with specific prompts — what takes hours manually takes minutes with prompts
- Anti-patterns are opportunities, not criticisms — all code evolves and accumulates tech debt naturally
- Prioritization is key: use the impact/risk matrix to decide what's worth fixing
- This connects directly with refactoring: each anti-pattern is a candidate for Module 4
Next capsule: Module Project — Architecture Map of a Real Project. You're going to combine dependency maps, flow analysis, and pattern identification into a complete document that serves as a base for refactoring decisions.
Additional Resources
- Refactoring: Improving the Design of Existing Code - Martin Fowler - The definitive reference on refactoring and code smells
- Design Patterns - Gang of Four - The original patterns explained
- AntiPatterns - Brown et al. - A complete catalog of software anti-patterns
- Python Design Patterns - Patterns specific to Python
- Code Smells - Refactoring Guru - A visual catalog of code smells with suggested refactorings
- Cyclomatic Complexity - Radon - A Python tool to measure cyclomatic complexity
Module 3, Capsule 04 — Refactoring & Legacy Code with Claude Code Guide