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

CriterionManualClaude Code
Finding god objectsReview each file manuallyPrompt: "classes with 500+ lines"
Circular dependenciesTrace imports file by filePrompt: "mutual imports"
Dead codeSearch each function and check callersPrompt: "uncalled functions"
Time for a module2-4 hours15-30 minutes
ConsistencyDepends on experienceConsistent with each prompt
False positivesFewer (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:

  1. Identified patterns: what patterns the codebase uses, where, and whether they're consistent
  2. 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:

  1. The route handlers only have 5 lines and delegate all the logic to classes in services/
  2. All DB access goes through classes in repositories/ — the services never do SQL
  3. When an order is created, an event is published that 4 different handlers process
  4. There's a function that receives a string "email"/"sms"/"push" and returns the right notifier
See solution
  1. Service Layer — logic in services, thin controllers
  2. Repository Pattern — centralized data access
  3. Event-Driven / Pub-Sub — communication by events
  4. 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:

  1. UserManager has 1500 lines and 35 methods
  2. 12 unused imports scattered across the project
  3. auth_service and user_service have a circular dependency
  4. 3 functions of 200+ lines with deep nesting
  5. 5 functions that are never called (dead code)
See solution
FindingImpactRiskPriorityReason
Unused importsLowLow🔄 ConvenientDoesn't affect functionality, easy to clean
Dead code (5 functions)MediumLow✅ Do firstReduces confusion, 0 risk of breaking something
Circular dependencyHighMedium⚠️ PlanAffects testability, requires restructuring
UserManager god objectHighHigh⚠️ Plan carefullyMaximum impact but risky to split
Deep nesting (3 functions)MediumMedium✅ Do secondImproves 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

  1. Refactoring: Improving the Design of Existing Code - Martin Fowler - The definitive reference on refactoring and code smells
  2. Design Patterns - Gang of Four - The original patterns explained
  3. AntiPatterns - Brown et al. - A complete catalog of software anti-patterns
  4. Python Design Patterns - Patterns specific to Python
  5. Code Smells - Refactoring Guru - A visual catalog of code smells with suggested refactorings
  6. Cyclomatic Complexity - Radon - A Python tool to measure cyclomatic complexity

Module 3, Capsule 04 — Refactoring & Legacy Code with Claude Code Guide