Module 4: Coordinated Multi-File Refactoring

Module Project: Coordinated Refactoring

Module Project: Coordinated Refactoring

Project description

This project is the proof that you can execute multi-file refactoring professionally. You're going to take a codebase with real structural problems — mixed logic, inconsistent naming, duplication, god objects — and improve it using the four techniques you learned: rename, extract, move, and interface changes. All coordinated with Claude Code and backed by regression tests.

The difference from the exercises in the previous capsules is the scale and the integration. Here you don't apply an isolated technique — you combine several in sequence to achieve a significant structural improvement. The refactoring touches 5+ files and requires real coordination.

The scenario simulates a real ticket: "Refactor the orders module to separate responsibilities and improve maintainability." It's not a rewrite — it's incremental improvement of what exists, preserving behavior.

This project connects directly with Module 8 (Capstone Project), where refactoring is one of the steps of the full migration of a legacy project.


Project Objective

Execute a coordinated refactoring on a real codebase that touches 5+ files, using Claude Code as a coordinator and regression tests as a safety net.

By completing this project:

  • ✅ You'll have executed at least 3 types of refactoring (rename, extract, move, or interface change)
  • ✅ You'll have written regression tests BEFORE each refactoring
  • ✅ You'll have verified that tests pass AFTER each refactoring
  • ✅ You'll have improved the structure of a codebase without changing its behavior
  • ✅ You'll have documented each change with justification

Technical Specifications

Codebase to Refactor

Option A — Provided codebase (recommended):

Create a mini-project with these intentional problems:

mkdir refactoring-project && cd refactoring-project
python -m venv venv && source venv/bin/activate
pip install fastapi uvicorn pydantic pytest

Create these files with the structural problems you're going to refactor:

src/app.py — A god object with mixed logic:

from fastapi import FastAPI, HTTPException
import json
from datetime import datetime

app = FastAPI()
DB = {"users": {}, "orders": {}, "products": {}}
NEXT_ID = {"users": 1, "orders": 1}

# === Everything mixed in a single file ===

@app.post("/users")
def create_user(data: dict):
    # Inline validation (should be in validators)
    if not data.get("name") or len(data["name"]) < 2:
        raise HTTPException(400, "Invalid name")
    if not data.get("email") or "@" not in data["email"]:
        raise HTTPException(400, "Invalid email")
    for u in DB["users"].values():
        if u["email"] == data["email"]:
            raise HTTPException(409, "Email exists")
    
    uid = NEXT_ID["users"]
    NEXT_ID["users"] += 1
    user = {"id": uid, "name": data["name"], "email": data["email"],
            "created_at": datetime.now().isoformat()}
    DB["users"][uid] = user
    return user

@app.get("/users/{user_id}")
def get_user(user_id: int):
    if user_id not in DB["users"]:
        raise HTTPException(404, "User not found")
    return DB["users"][user_id]

@app.post("/orders")
def create_order(data: dict):
    # Inline validation (duplicated with the create_user pattern)
    if not data.get("user_id"):
        raise HTTPException(400, "Missing user_id")
    if data["user_id"] not in DB["users"]:
        raise HTTPException(404, "User not found")
    if not data.get("items") or len(data["items"]) == 0:
        raise HTTPException(400, "No items")
    
    # Inline calculation (should be its own function)
    subtotal = 0
    for item in data["items"]:
        if item.get("product_id") not in DB["products"]:
            raise HTTPException(404, f"Product {item.get('product_id')} not found")
        product = DB["products"][item["product_id"]]
        subtotal += product["price"] * item.get("quantity", 1)
    
    tax = subtotal * 0.16
    shipping = 9.99 if subtotal < 50 else 0
    total = subtotal + tax + shipping
    
    oid = NEXT_ID["orders"]
    NEXT_ID["orders"] += 1
    order = {"id": oid, "user_id": data["user_id"], "items": data["items"],
             "subtotal": round(subtotal, 2), "tax": round(tax, 2),
             "shipping": round(shipping, 2), "total": round(total, 2),
             "status": "pending", "created_at": datetime.now().isoformat()}
    DB["orders"][oid] = order
    return order

@app.get("/orders/{order_id}")
def get_order(order_id: int):
    if order_id not in DB["orders"]:
        raise HTTPException(404, "Order not found")
    return DB["orders"][order_id]

@app.post("/products")
def create_product(data: dict):
    pid = data.get("id", len(DB["products"]) + 1)
    product = {"id": pid, "name": data["name"], "price": data["price"]}
    DB["products"][pid] = product
    return product

# Utility function that should be in utils/
def format_price(amount):
    return f"${amount:.2f}"

# Another function that should be in utils/
def validate_email(email):
    return "@" in email and "." in email.split("@")[1]

Option B — Your own project:

If you have a real project with similar problems, use it. Requirements: at least 3 files, at least 2 clear structural problems.

Tools

  • Claude Code
  • pytest for regression tests
  • Git for tracking changes (1 commit per refactoring)

Refactoring Plan (5 steps)

Step 1: Assessment (identify problems)

Use Claude Code to analyze the codebase:

> "Analyze src/app.py and identify structural problems:
   god objects, mixed logic, duplication, functions that
   should be in other files. List each problem
   with severity and a refactoring suggestion."

Expected problems:

  1. God file: everything in app.py (500+ lines)
  2. Duplicated inline validation (users and orders validate the same way)
  3. Inline price calculation (should be a separate function)
  4. Utils mixed with routes (format_price, validate_email)
  5. No separation of layers (routes, services, validators)

Step 2: Regression tests (BEFORE changing anything)

> "Before any refactoring, write complete regression tests
   for all the endpoints: create_user, get_user,
   create_order, get_order, create_product. Include happy paths,
   validations, and error handling. Run and confirm green."

Step 3: Refactoring 1 — Extract utils

> "Extract format_price() and validate_email() from app.py into
   a new file src/utils.py. Update the imports in app.py.
   Run tests."

Step 4: Refactoring 2 — Extract services

> "Extract the business logic of create_order() into a new
   OrderService class in src/services/order_service.py.
   OrderService should have methods: validate_order(),
   calculate_total(), create(). The endpoint in app.py only
   calls OrderService. Run tests."

Step 5: Refactoring 3 — Extract validators + Rename

> "Extract all the validation logic into src/validators.py.
   Create functions: validate_user_data(), validate_order_data().
   Remove the duplicated validation in the endpoints.
   Rename the endpoint functions for clarity:
   create_user → handle_create_user (route handler).
   Run tests."

Deliverable

Expected structure after the refactoring

refactoring-project/
├── src/
│   ├── app.py                 # Only routes (thin)
│   ├── services/
│   │   └── order_service.py   # Business logic
│   ├── validators.py          # Centralized validation
│   └── utils.py               # Pure utilities
├── tests/
│   ├── test_regression.py     # Original regression tests
│   ├── test_order_service.py  # Tests for the extracted service
│   └── test_validators.py     # Validator tests
├── REFACTORING_LOG.md         # Change documentation
└── requirements.txt

REFACTORING_LOG.md

# Refactoring Log

## Assessment
- [List of problems found with severity]

## Refactoring 1: Extract Utils
- **What:** Move format_price and validate_email to utils.py
- **Why:** Utility functions don't belong in the routes file
- **Affected files:** app.py, utils.py (new)
- **Tests:** ✅ All pass before and after

## Refactoring 2: Extract OrderService
- **What:** Extract the order logic to OrderService
- **Why:** app.py is a god file, the business logic should be separated
- **Affected files:** app.py, services/order_service.py (new)
- **Tests:** ✅ All pass before and after

## Refactoring 3: Extract Validators + Rename
- **What:** Centralize validation, rename handlers
- **Why:** Duplicated validation, inconsistent naming
- **Affected files:** app.py, validators.py (new)
- **Tests:** ✅ All pass before and after

## Metrics
| Metric | Before | After |
|---------|-------|---------|
| Files | 1 | 5 |
| Lines in app.py | ~100 | ~40 |
| Duplicated functions | 2 | 0 |
| Tests | 0 | 15+ |

Success Criteria

Your project is complete when:

  • ✅ At least 3 different refactorings executed (rename, extract, move, or interface change)
  • ✅ Regression tests written BEFORE each refactoring
  • ✅ Tests pass AFTER each refactoring
  • ✅ The codebase has a better structure without a change in behavior
  • ✅ REFACTORING_LOG.md documents each change with justification
  • ✅ At least 5 files were affected in total
  • ✅ The Git history shows 1 commit per refactoring (not all together)

Evaluation Rubric (100 points)

Regression Tests (30 points)

  • (10 pts) Tests written BEFORE each refactoring
  • (10 pts) Tests cover happy paths, validations, and errors
  • (10 pts) Tests pass before AND after each refactoring

Refactoring Quality (40 points)

  • (10 pts) At least 3 types of refactoring applied
  • (10 pts) Behavior preserved (tests green at each step)
  • (10 pts) Structure significantly improved
  • (10 pts) 5+ files affected with coherent changes

Documentation (20 points)

  • (10 pts) Complete REFACTORING_LOG with assessment, changes, and metrics
  • (5 pts) Clean Git history (1 commit per refactoring)
  • (5 pts) Each refactoring has a clear justification

Process (10 points)

  • (5 pts) Logical refactoring order (lowest to highest risk)
  • (5 pts) Effective use of Claude Code as a coordinator

Extra Credit (up to +10 points)

  • (+3 pts) 4+ types of refactoring applied
  • (+3 pts) Regression tests + unit tests for new code
  • (+2 pts) Before/after metrics (lines, complexity)
  • (+2 pts) Before/after architecture diagram

Common Errors

Error 1: Refactoring without tests first

Mistake #1. If a test fails afterward and you didn't have tests before, you don't know whether the refactoring caused it or whether it was already broken.

Error 2: Doing all the refactorings in a single commit

If something fails, you can't tell which refactoring caused it. One commit per refactoring lets you revert only the problematic one.

Error 3: Changing behavior during the refactoring

"While I'm here, I'll fix this bug too." No. Refactoring and bug fix are separate commits. Mixing them makes it impossible to verify that the refactoring preserved behavior.

Error 4: Not verifying tests after EACH refactoring

Verifying only at the end accumulates errors. If refactoring 2 broke something, but you continue with 3, now you have 2 stacked problems.

Error 5: A final structure worse than the original

Sometimes the refactoring creates more files than necessary, or separates things that should be together. Verify that the final structure makes sense, not just that the tests pass.

Error 6: Not documenting the "why"

"I moved X to Y" without explaining why is useless documentation. "I moved X to Y because X contained business logic that doesn't belong in the routing layer" is useful documentation.

Error 7: An overly ambitious refactoring

A refactoring that touches 20 files carries high risk. Prefer 3 refactorings of 5-7 files each over 1 massive refactoring.


Resources for the Project

  1. Refactoring Guru - Catalog - A reference of all the types of refactoring with examples
  2. pytest Documentation - To write regression tests
  3. FastAPI Testing - Testing FastAPI endpoints with TestClient
  4. Git Best Practices for Refactoring - Atomic commits and descriptive messages
  5. Martin Fowler - Refactoring - The site of the author of the refactoring bible

Connection with the Next Module

What you learned here — refactoring within the same framework — scales up in Module 5: Framework Migration. The transition is:

"You already know how to refactor within the same framework — rename, extract, move, change interface. But what happens when you need to change frameworks? Flask→FastAPI, sync→async, unittest→pytest. That's migration: full-framework-scale refactoring."

The techniques are the same (tests first, coordinated changes, verification). The scale is larger.