Lesson 15 of 46 ~30 min
Course progress
0%

Multi-File Refactoring with Full Codebase Context

Execute large-scale refactoring across hundreds of files — rename refactoring, interface extraction, dependency inversion — using Opus 4.6's state-of-the-art coding capabilities.

Opus 4.6 scores 80.8% on SWE-bench Verified — the highest of any model. SWE-bench tests whether a model can autonomously resolve real GitHub issues from real open source projects. This is not a synthetic benchmark. It measures exactly what you need: can the model understand a large codebase, locate the problem, and apply a correct multi-file fix?

This lesson teaches you to use that capability for systematic refactoring — the kind of work that takes a senior engineer days and a team weeks.

Why Opus 4.6 Changes Refactoring

Traditional AI-assisted refactoring works file by file. You open a file, ask for changes, then manually propagate those changes across dependent files. With Opus 4.6:

graph LR
    subgraph "Previous Models (200K context)"
        A1[File A] --> B1[Refactor A]
        B1 --> C1[Manually find dependents]
        C1 --> D1[Fix File B]
        D1 --> E1[Fix File C]
        E1 --> F1[Hope nothing was missed]
    end

    subgraph "Opus 4.6 (1M context)"
        A2[Entire Codebase] --> B2[Analyze all dependencies]
        B2 --> C2[Refactor all files atomically]
        C2 --> D2[Verify consistency]
    end

The 1M token window means Opus 4.6 can hold your entire codebase in context while performing the refactor. It sees every import, every type reference, every test assertion — simultaneously.

Rename Refactoring at Scale

Renaming seems simple until you realize it touches types, interfaces, database column mappings, API contracts, test fixtures, documentation, and configuration files.

Example: Renaming a Core Domain Concept

Your team decides that Customer should become Account across the entire codebase. This affects:

  • TypeScript interfaces and types
  • Database models and migrations
  • API endpoint paths and request/response schemas
  • Test fixtures and assertions
  • Documentation and comments
claude "Rename the domain concept 'Customer' to 'Account' across 
  the entire codebase. This includes:
  
  1. All TypeScript types, interfaces, and classes
  2. All variable names and function parameters
  3. Database model definitions (but NOT migration files — create new migrations)
  4. API route paths: /customers → /accounts
  5. Test files and fixtures
  6. Documentation and comments
  
  Do NOT modify:
  - Migration files in db/migrations/ (create new ones instead)
  - Third-party type definitions in node_modules/
  - Git history or commit messages
  
  After making changes, run 'npm run typecheck' and 'npm run test' 
  to verify nothing is broken."

What Opus 4.6 Does Internally

When given this instruction with full codebase context, Opus 4.6:

  1. Builds a dependency graph — traces every reference to Customer across all files
  2. Plans the change order — types first, then implementations, then tests
  3. Handles edge casesCustomerServiceAccountService, customer_idaccount_id, getCustomerByIdgetAccountById
  4. Preserves semantics — does not rename customerId in a Stripe integration where it refers to Stripe’s concept
  5. Generates migrations — creates a new database migration for column renames
  6. Runs verification — executes typecheck and tests to confirm the refactor

Interface Extraction

Extracting interfaces from concrete implementations is a refactoring pattern that improves testability and decoupling. It requires understanding every usage site to determine what the interface should contain.

claude "Extract an interface from the UserService class.

  Requirements:
  1. Analyze every file that imports or uses UserService
  2. Determine the minimal interface needed based on actual usage
  3. Create IUserService in packages/shared/src/interfaces/
  4. Update all consumers to depend on IUserService instead of UserService
  5. Update dependency injection container in packages/api/src/container.ts
  6. Update all test files to mock IUserService instead of UserService
  
  The interface should only include methods that are actually used 
  by consumers — not every public method on UserService."

The Extracted Interface

Opus 4.6 analyzes usage across the codebase and produces a minimal interface:

// packages/shared/src/interfaces/IUserService.ts
export interface IUserService {
  getById(id: string): Promise<Result<User, NotFoundError>>;
  getByEmail(email: string): Promise<Result<User, NotFoundError>>;
  update(id: string, data: UpdateUserDTO): Promise<Result<User, ValidationError>>;
  deactivate(id: string): Promise<Result<void, NotFoundError>>;
}

Note what is not in the interface: internal methods like hashPassword(), validateEmailDomain(), or syncToExternalCRM() — because no consumer calls them directly.

Dependency Inversion Across Modules

Dependency inversion — making high-level modules depend on abstractions rather than concrete implementations — is one of the hardest refactoring patterns to apply retroactively. It requires understanding the full dependency tree.

claude "Apply dependency inversion to the notification subsystem.

  Current state:
  - OrderService directly imports and instantiates EmailNotifier
  - PaymentService directly imports and instantiates SlackNotifier  
  - UserService directly imports and instantiates EmailNotifier
  
  Target state:
  - Create a NotificationPort interface
  - Create EmailAdapter and SlackAdapter implementing NotificationPort
  - All services receive NotificationPort via constructor injection
  - Wire everything in the DI container
  - Update all tests to inject mock NotificationPort
  
  Show me the dependency graph before and after."

Before and After Dependency Graph

Opus 4.6 generates the refactoring and provides the analysis:

graph TD
    subgraph "Before: Direct Dependencies"
        OS1[OrderService] --> EN1[EmailNotifier]
        PS1[PaymentService] --> SN1[SlackNotifier]
        US1[UserService] --> EN2[EmailNotifier]
    end
graph TD
    subgraph "After: Dependency Inversion"
        OS2[OrderService] --> NP[NotificationPort]
        PS2[PaymentService] --> NP
        US2[UserService] --> NP
        NP --> EA[EmailAdapter]
        NP --> SA[SlackAdapter]
        DI[DI Container] -.->|wires| EA
        DI -.->|wires| SA
    end

Practical Refactoring Workflow

A battle-tested workflow for large refactoring operations:

Step 1: Analysis Phase

# Always start with analysis, not changes
claude "Analyze the impact of extracting the payment processing logic 
  from OrderService into a dedicated PaymentService.
  
  List:
  1. Every file that would need to change
  2. Every test that would need to update
  3. Risk areas where the refactor could introduce bugs
  4. A suggested order of operations
  
  Do NOT make any changes yet."

Step 2: Create a Safety Net

# Create a refactoring branch
git checkout -b refactor/extract-payment-service

# Ensure all tests pass before refactoring
npm run test

Step 3: Execute in Phases

# Phase 1: Create new structures
claude "Create the PaymentService class and its interface based on 
  the analysis from our previous conversation. Do not modify 
  existing files yet."

# Phase 2: Wire the new service
claude "Update the DI container to register PaymentService. 
  Update OrderService to use PaymentService via injection. 
  Run typecheck after changes."

# Phase 3: Migrate tests
claude "Update all tests affected by the PaymentService extraction. 
  Run the full test suite and fix any failures."

Step 4: Verify

# Run full verification
npm run typecheck && npm run lint && npm run test

# Review the diff
git diff --stat

Handling Refactoring Failures

Opus 4.6 is the best available model for refactoring, but it is not infallible. Common failure modes and mitigations:

Failure ModeDetectionMitigation
Missed type referenceTypeScript compiler errorRun tsc --noEmit after each phase
Broken circular dependencyRuntime import errorAsk Opus to analyze the import graph before changing
Test assertion driftTest failures with wrong expected valuesReview test changes manually — do not auto-approve
API contract changeIntegration test failuresLock API schemas with snapshot tests
Over-abstractionCode is harder to readConstrain: “Only extract if 3+ consumers exist”

Cost Awareness

Large refactoring operations consume significant tokens:

Typical rename refactor (medium project):
  Input:  ~300,000 tokens (codebase context)
  Output: ~15,000 tokens (changes + explanations)
  Cost:   ~$1.88 per run

Dependency inversion (large project):
  Input:  ~600,000 tokens  
  Output: ~30,000 tokens
  Cost:   ~$3.75 per run

Compare this to the engineering time saved: a multi-day refactoring task completed in minutes, with fewer missed references than a manual approach. The ROI is clear for any professional team.

When Not to Use Autonomous Refactoring

Opus 4.6 refactoring works best for mechanical, well-defined transformations. Avoid it for:

  • Architectural redesigns — changing from monolith to microservices requires human judgment about service boundaries
  • Performance optimization — requires profiling data that the model cannot generate
  • Legacy code without tests — no safety net means no way to verify correctness
  • Cross-repository changes — Claude Code operates within a single project context

For these cases, use Opus 4.6 as an advisor — ask for analysis and recommendations — but apply changes manually.

In the next lesson, you will learn to use Opus 4.6 for automated security auditing — the capability that discovered over 500 zero-day vulnerabilities.