Module 4: Coordinated Multi-File Refactoring
Rename and Extract Refactoring Cross-File
Rename and Extract Refactoring Cross-File
Capsule description
Rename and extract are the two most frequent refactorings in daily work. A rename changes the name of a function, class, or variable and updates all the references in the codebase. An extract takes a block of logic within a function and turns it into a separate function or class. Both sound simple — but when they touch 10, 15, or 20 files, the complexity multiplies.
In this capsule you're going to execute both refactorings with Claude Code as a coordinator. The difference from doing it manually or with an IDE is that Claude Code understands the semantic context: it doesn't just search and replace text, it understands which references are of the same concept and which are name coincidences. And when you extract logic, Claude Code creates the new module, moves the code, adds imports, and updates callers — all coordinated.
Before each refactoring, you write regression tests. Afterward, you verify that they pass. That cycle is non-negotiable.
Rename Refactoring
Why naming matters so much
An incorrect or inconsistent name multiplies the cost of understanding code. If a function is called process_data() but what it does is validate emails, every developer who reads it wastes time deciphering the discrepancy. Rename doesn't change behavior — it changes comprehension.
The multi-file rename problem
# The simplest rename: a function used in 1 file
# → The IDE resolves it in 2 seconds
# The real rename: a class used in 12 files
# src/services/user_handler.py → class UserHandler:
# src/api/routes/users.py → from services.user_handler import UserHandler
# src/api/routes/admin.py → from services.user_handler import UserHandler
# src/services/order_service.py → from services.user_handler import UserHandler
# src/tasks/cleanup.py → from services.user_handler import UserHandler
# src/tasks/reports.py → from services.user_handler import UserHandler
# tests/test_user_handler.py → from services.user_handler import UserHandler
# tests/test_orders.py → from services.user_handler import UserHandler
# tests/test_admin.py → from services.user_handler import UserHandler
# docs/api_reference.md → Mentions UserHandler
# config/services.yaml → user_handler: enabled
# README.md → "UserHandler manages..."
That's 12 files. Forgetting one = an import error at runtime. An IDE can resolve the Python imports, but it doesn't update YAML, markdown, or comments. Claude Code updates everything.
Rename cycle with Claude Code
Step 1: Regression tests
# Before renaming, make sure the tests pass:
> "Run the project's tests and confirm that they all pass"
# Expected output:
# ✅ 47 tests passed, 0 failed
# If there are failing tests BEFORE the rename, fix that first
Step 2: Identify all the references
# Ask Claude Code to find ALL the references:
> "Find all the references to UserHandler in the project.
Include imports, direct use, strings, YAML, markdown,
comments, and docstrings. List each file and line."
# Expected output:
# Python imports: 8 files
# Direct use: 15 references in 8 files
# Strings/YAML: 2 references
# Markdown/docs: 3 references
# Comments: 4 references
# Total: 32 references in 12 files
Step 3: Execute the rename
# Give the complete instruction:
> "Rename UserHandler to UserService across the whole project.
Update:
1. The class definition
2. The file name (user_handler.py → user_service.py)
3. All the imports
4. All the references in code
5. References in YAML, markdown, and comments
6. Test names (test_user_handler → test_user_service)"
# Claude Code coordinates the changes across all the files
Step 4: Verify
# Run the tests:
> "Run the tests and confirm that they all pass after
the rename"
# Expected output:
# ✅ 47 tests passed, 0 failed
# If any fails → Claude Code forgot a reference → fix it
Progressive rename example
Basic — Function rename:
# Before: a function with a vague name
# src/utils/helpers.py
def process(data):
"""Validates that the email has the correct format."""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, data))
# Prompt to Claude Code:
> "Rename the process() function in src/utils/helpers.py
to validate_email(). Update all the references
in the project."
# After:
def validate_email(data):
"""Validates that the email has the correct format."""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, data))
# + updates all the callers:
# src/services/user_service.py: process(email) → validate_email(email)
# src/api/routes/auth.py: process(data["email"]) → validate_email(data["email"])
# tests/test_helpers.py: test_process() → test_validate_email()
Intermediate — Class + file rename:
# Prompt:
> "Rename the DataProcessor class in src/processing/data_processor.py
to OrderCalculator. Also rename the file to order_calculator.py.
Update all the imports, references, tests, and documentation."
# Claude Code executes:
# 1. Renames the class DataProcessor → OrderCalculator
# 2. Renames the file data_processor.py → order_calculator.py
# 3. Updates 8 imports in other files
# 4. Updates 3 references in tests
# 5. Updates 2 mentions in README.md
Advanced — Rename with semantic discrimination:
# Problem: "process" appears in 50 places, but only
# 12 are the function you want to rename
# Prompt:
> "Rename the process() function of the OrderService class
in src/services/order_service.py to calculate_total().
Rename ONLY the references to THIS specific function,
not to other functions called 'process' in other modules."
# Claude Code understands the context and only changes the
# references that point to OrderService.process(),
# leaving the other 'process' functions intact
Extract Refactoring
When to extract
You extract when a function does too much, when logic is repeated in several places, or when a block of code has a clear responsibility that deserves its own function/class.
Signs that you need extract
# Sign 1: A function of 100+ lines
def create_order(request):
# 30 lines of validation
# 20 lines of price calculation
# 15 lines of coupon processing
# 20 lines of card charge
# 15 lines of notifications
pass # Total: 100 lines, 5 responsibilities
# Sign 2: Duplicated code
# In order_service.py:
tax = subtotal * 0.16
if region == "EU":
tax = subtotal * 0.21
# In invoice_service.py (identical):
tax = subtotal * 0.16
if region == "EU":
tax = subtotal * 0.21
# Sign 3: A comment that describes a block
def process_payment(order):
# --- Validate payment method ---
if not order.payment_method:
raise ValueError("No payment method")
if order.payment_method.expired:
raise ValueError("Payment method expired")
# (If you need a comment to separate, it's a candidate for extract)
Extract function with Claude Code
Step 1: Identify what to extract
# Prompt:
> "Analyze the create_order() function in src/services/order_service.py.
Which logic blocks have their own responsibility and are
candidates to be extracted as separate functions?"
# Claude Code identifies:
# 1. Request validation (lines 15-45)
# 2. Price calculation (lines 47-67)
# 3. Coupon processing (lines 69-83)
# 4. Card charge (lines 85-104)
# 5. Sending notifications (lines 106-120)
Step 2: Regression tests
> "Before refactoring, write tests that capture the
current behavior of create_order(). Include:
- Happy path (order created successfully)
- Failed validation (missing data)
- Invalid coupon
- Rejected payment"
Step 3: Extract
# Prompt:
> "Extract the price calculation logic (lines 47-67)
from create_order() into a new function calculate_order_total()
in the same file. The new function should receive the items
and the coupon, and return the calculated total. Update
create_order() to call calculate_order_total()."
# Before:
def create_order(request):
# ... validation ...
subtotal = sum(item.price * item.quantity for item in items)
if coupon:
discount = subtotal * coupon.discount_percent / 100
subtotal -= discount
tax = subtotal * TAX_RATE
total = subtotal + tax + shipping
# ... card charge ...
# After:
def calculate_order_total(items, coupon=None, shipping=0):
"""Computes the total of an order with discount and tax."""
subtotal = sum(item.price * item.quantity for item in items)
if coupon:
discount = subtotal * coupon.discount_percent / 100
subtotal -= discount
tax = subtotal * TAX_RATE
return subtotal + tax + shipping
def create_order(request):
# ... validation ...
total = calculate_order_total(items, coupon, shipping)
# ... card charge ...
Step 4: Verify
> "Run the create_order tests and confirm that
they all pass after the extract"
Extract class with Claude Code
When the extracted logic deserves its own module:
# Prompt:
> "The price calculation logic in order_service.py
is complex and is also used in invoice_service.py.
Extract all the pricing logic into a new class
PricingEngine in src/services/pricing_engine.py.
The class should have methods:
- calculate_subtotal(items)
- apply_discount(subtotal, coupon)
- calculate_tax(amount, region)
- calculate_total(items, coupon, region, shipping)
Update order_service.py and invoice_service.py to
use PricingEngine instead of duplicated code."
# Claude Code:
# 1. Creates src/services/pricing_engine.py with the class
# 2. Moves the pricing logic from order_service.py
# 3. Moves the duplicated logic from invoice_service.py
# 4. Adds imports of PricingEngine in both services
# 5. Updates both services to use PricingEngine
Comparison: Manual Refactoring vs Claude Code
| Criterion | Manual / IDE | Claude Code |
|---|---|---|
| Simple rename | The IDE does it well | Equivalent |
| Rename in YAML/docs | Manual | Automatic |
| Extract function | The IDE suggests | Claude Code understands the context |
| Extract class cross-file | Manual, tedious | Coordinated automatically |
| Updating tests | Manual | Automatic |
| Semantic discrimination | Hard | Natural (it understands the context) |
| Verification | Run tests manually | Claude Code runs and reports |
Claude Code's value: it's not that it does impossible things — it's that it coordinates changes across N files that manually would take 10x more time and are prone to errors.
Connection with the Project
In the Module Project (capsule 06) you're going to execute a coordinated refactoring on a real codebase. The rename and extract techniques from this capsule are the building blocks:
- Rename to fix inconsistent naming
- Extract function to separate responsibilities in long functions
- Extract class to eliminate duplication by creating shared abstractions
Troubleshooting
Problem 1: Claude Code renames references it shouldn't
Cause: There's another function/variable with the same name in another module.
Solution: Be specific about which one to rename:
> "Rename ONLY the process() function of OrderService
in src/services/order_service.py. Do NOT rename
process() in payment_service.py or in utils.py"
Problem 2: Tests fail after the extract
Cause: The extracted function has a bug or is missing parameters.
Solution: Compare the before/after behavior:
> "The tests fail after the extract. Compare the
original function with the extracted one and find what
changed in the behavior."
Problem 3: Circular import after the extract
Cause: The new module imports something that in turn imports the original module.
Solution: Restructure the imports or move the dependency:
> "The extract of PricingEngine caused a circular import
between pricing_engine.py and order_service.py. How
can I restructure to eliminate it?"
Problem 4: I don't know what to extract first
Cause: The function has many candidate blocks.
Solution: Extract one at a time, from the inside out:
# First the most internal blocks (helper functions)
# Then the most external blocks (service functions)
# Never extract 3 things at once
Problem 5: The rename breaks the configuration
Cause: References in YAML, JSON, or .env files that Claude Code didn't detect.
Solution: Ask for an exhaustive search:
> "Search for the string 'user_handler' in ALL the project
files, including YAML, JSON, .env, Dockerfiles,
and CI/CD configuration files"
Exercises
Exercise 1: Identify rename candidates (Easy)
Look at these function names and propose better names:
def do_stuff(data): # In user_service.py, validates and saves users
def run(config): # In email_sender.py, sends an email
def handle(event): # In payment_processor.py, processes a payment
def get(id): # In product_repo.py, looks up a product by ID
See solution
def validate_and_save_user(data): # Describes what it does
def send_email(config): # Specific verb
def process_payment(event): # Domain + action
def get_product_by_id(id): # Entity + criterion
Rule: a good name answers "what does this function do?" without reading the code. Verb + noun + context.
Exercise 2: Plan a cross-file rename (Easy)
Your project has a DataManager class that only handles users. It's imported in 8 files. Write the 4 steps of the rename cycle with the exact prompts you'd give Claude Code.
See solution
# Step 1: Verify tests
> "Run all the tests and confirm that they pass"
# Step 2: Find references
> "Find all the references to DataManager in the
project: imports, direct use, strings, docs, YAML,
comments. List each file and line."
# Step 3: Execute rename
> "Rename DataManager to UserService across the whole project.
Also rename the file data_manager.py to
user_service.py. Update all the imports, references,
tests, docs, and configuration."
# Step 4: Verify
> "Run all the tests and confirm that they pass after
the rename."
Exercise 3: Identify extract candidates (Medium)
Analyze this function and propose what to extract:
def process_order(order_data):
# Validate
if not order_data.get("items"):
raise ValueError("No items")
if not order_data.get("user_id"):
raise ValueError("No user")
for item in order_data["items"]:
if item["quantity"] < 1:
raise ValueError(f"Invalid quantity: {item['quantity']}")
# Calculate
subtotal = sum(i["price"] * i["quantity"] for i in order_data["items"])
tax = subtotal * 0.16
shipping = 9.99 if subtotal < 50 else 0
total = subtotal + tax + shipping
# Save
order = Order(user_id=order_data["user_id"], total=total)
db.session.add(order)
for item in order_data["items"]:
order_item = OrderItem(order_id=order.id, **item)
db.session.add(order_item)
db.session.commit()
# Notify
send_email(order_data["user_id"], f"Order {order.id} confirmed")
publish_event("order_created", {"order_id": order.id})
return order
See solution
4 candidates to extract:
# Extract 1: Validation
def validate_order_data(order_data: dict) -> None:
"""Validates that the order data is correct."""
if not order_data.get("items"):
raise ValueError("No items")
if not order_data.get("user_id"):
raise ValueError("No user")
for item in order_data["items"]:
if item["quantity"] < 1:
raise ValueError(f"Invalid quantity: {item['quantity']}")
# Extract 2: Calculation
def calculate_order_totals(items: list) -> dict:
"""Computes subtotal, tax, shipping, and total."""
subtotal = sum(i["price"] * i["quantity"] for i in items)
tax = subtotal * 0.16
shipping = 9.99 if subtotal < 50 else 0
return {"subtotal": subtotal, "tax": tax, "shipping": shipping, "total": subtotal + tax + shipping}
# Extract 3: Persistence
def save_order(user_id: int, total: float, items: list) -> Order:
"""Saves the order and its items in the DB."""
order = Order(user_id=user_id, total=total)
db.session.add(order)
for item in items:
db.session.add(OrderItem(order_id=order.id, **item))
db.session.commit()
return order
# Extract 4: Notification
def notify_order_created(user_id: int, order_id: int) -> None:
"""Sends an email and publishes an order-created event."""
send_email(user_id, f"Order {order_id} confirmed")
publish_event("order_created", {"order_id": order_id})
Result: process_order() goes from 25 lines with 4 responsibilities to 5 lines that call 4 specialized functions.
Exercise 4: Extract with Claude Code (Medium)
Write the complete prompt for Claude Code that extracts the calculation logic from the previous exercise, including: creating the new function, updating process_order(), and writing tests for the extracted function.
See solution
> "In src/services/order_service.py, extract the price
calculation logic (the lines that compute subtotal, tax,
shipping, and total) from process_order() into a new function
calculate_order_totals(items).
The new function should:
1. Receive a list of items (each with price and quantity)
2. Return a dict with subtotal, tax, shipping, and total
3. Be in the same file, before process_order()
Update process_order() to call
calculate_order_totals() instead of the inline code.
Write unit tests for calculate_order_totals()
in tests/test_order_service.py that cover:
- A single item
- Multiple items
- An order with free shipping (subtotal >= 50)
- An order with shipping (subtotal < 50)
After making the changes, run all the tests."
Exercise 5: Combined Rename + Extract (Hard)
You have a Helper class in src/utils/helper.py with 30 static methods for everything (emails, calculations, validations, formatting). Design a refactoring plan in 3 phases using rename and extract.
See solution
# Phase 1: Extract by domain (without rename)
> "Analyze the 30 methods of Helper in src/utils/helper.py
and group them by domain: email, calculations, validations,
formatting. List each method with its group."
# Result: 4 groups of 6-8 methods each
# Phase 2: Extract classes
> "Extract the methods of the 'email' group from Helper into a
new class EmailUtils in src/utils/email_utils.py.
Update all the callers of Helper.send_email()
to EmailUtils.send_email(). Run tests."
# Repeat for each group:
# Helper.calculate_* → CalculationUtils
# Helper.validate_* → ValidationUtils
# Helper.format_* → FormattingUtils
# Phase 3: Rename for clarity
> "Rename the methods of EmailUtils to be
more descriptive: send() → send_email(),
validate() → validate_email_format(),
parse() → parse_email_address(). Update references."
# Final result: 1 god class → 4 specialized classes
# with clear names and a single responsibility
Key: don't try to do everything in one step. Phase 1 groups, Phase 2 separates, Phase 3 cleans up. Tests at each step.
Summary
In this capsule you learned:
- Rename refactoring changes names and updates all the references — Claude Code handles YAML, docs, and configuration in addition to code
- Extract refactoring separates logic into new functions/classes — it eliminates duplication and reduces responsibilities
- The cycle is: tests → refactor → verify — always, no exceptions
- Claude Code discriminates semantically: it renames only the correct references, not text coincidences
- Progressive extract: first identify what to extract, then extract one at a time
- The multi-file coordination is the main value — Claude Code updates 10+ files coherently
Next capsule: Move Module and Updating Imports. You're going to learn to reorganize the file structure without breaking imports.
Additional Resources
- Refactoring Guru - Extract Method - Visual explanation of the extract method with before/after
- Refactoring Guru - Rename Method - Visual explanation of the rename method
- Martin Fowler - Refactoring Catalog - A complete catalog of refactorings with examples
- Python - Naming Conventions (PEP 8) - Naming conventions for Python
- Working Effectively with Legacy Code - Extract and Override - Extract techniques for untested code
- rope - Python Refactoring Library - A Python library for programmatic refactoring
Module 4, Capsule 02 — Refactoring & Legacy Code with Claude Code Guide