How I use Claude Code to refactor legacy code — without breaking production
Legacy code refactoring is one of the most dangerous things a developer can do. You're changing code that works (somehow) without fully understanding why. One wrong move and production breaks.
Here's my exact workflow using Claude Code that's let me safely refactor 50,000+ line legacy codebases.
The problem with legacy refactoring
Legacy code is scary because:
- No tests (or tests that don't actually test anything)
- No documentation
- Business logic buried in 10-year-old spaghetti
- The person who wrote it left in 2019
Most developers either avoid refactoring (accumulating debt) or refactor recklessly (breaking things). Claude Code gives you a third option.
My workflow
Step 1: Understand before touching
# Start a Claude Code session with the legacy file
claude
First prompt:
I need to refactor /src/billing/invoiceProcessor.js — a 2,300-line file written in 2016.
Before we touch any code:
1. Read the file and identify all the things it does
2. Find all other files that import or call functions from this file
3. Identify any implicit dependencies (globals, shared state, side effects)
4. List the 3 highest-risk areas to refactor
Do NOT change any code yet. Just map the territory.
This mapping step is critical. Claude Code reads the entire file and its callers, building a dependency graph. I've caught 3 production bugs just from this mapping step — code that was broken but nobody noticed.
Step 2: Add tests before refactoring
Now write characterization tests for invoiceProcessor.js.
These tests should capture the CURRENT behavior — even if that behavior is wrong.
Focus on:
- The 5 most-called public functions
- Any edge cases you spotted in the mapping step
- Any error handling paths
Write the tests to /tests/legacy/invoiceProcessor.test.js
Characterization tests are your safety net. They don't test what the code should do — they test what it does do. If your refactor changes any behavior, the tests catch it.
Step 3: Refactor in small slices
Now let's refactor one function at a time.
Start with getInvoiceTotal() — it's the simplest function.
Rules:
- Don't change the function signature
- Don't change the return type
- Run the tests after each change
- If tests fail, stop and show me what changed
The key is small, verifiable slices. Not "refactor the whole file" but "refactor this one function, verify it works, then move to the next."
Step 4: Extract modules incrementally
The invoiceProcessor.js does 4 distinct things:
1. Invoice calculation
2. Tax computation
3. PDF generation
4. Email sending
Let's extract tax computation into /src/billing/taxComputer.js
Keep the original function in invoiceProcessor.js but have it call taxComputer
This way we don't break any callers while we gradually migrate
This "strangler fig" pattern lets you refactor incrementally without a big-bang rewrite.
Step 5: The rate limit problem
Here's where most developers hit a wall: long legacy files exhaust Claude's context.
A 2,300-line file + its callers + tests can easily exceed the context window. I hit this problem constantly.
My solution: use SimplyLouie as my Claude Code API endpoint. At $2/month it gives me API access without the rate limits that stop sessions mid-refactor.
export ANTHROPIC_BASE_URL=https://simplylouie.com/api
export ANTHROPIC_API_KEY=your-key
claude # now rate-limit free
Legacy refactoring sessions run long. Really long. When you're mapping a 50,000-line codebase, you can't afford to lose your session to a rate limit.
Real results
Using this workflow, I've:
- Refactored a 2,300-line billing processor into 6 focused modules (zero production incidents)
- Added 140 characterization tests to a codebase with zero existing tests
- Reduced a 10-second invoice generation time to 0.8 seconds
- Found and fixed 3 latent bugs during the mapping phase (before touching any code)
The mindset shift
Legacy refactoring with Claude Code changes your approach from "I'll just rewrite this" to "I'll understand this first, protect it with tests, then improve it incrementally."
The characterization-tests-first approach is the key insight. It's not about fixing the code — it's about capturing the existing behavior so you can safely improve it.
Your turn
If you have a legacy file you've been avoiding, try this:
- Open Claude Code
- Ask it to map the file (not change it)
- Write 5 characterization tests
- Refactor one function
The first session will show you how much safer this approach is than just diving in.
If your refactoring sessions keep getting cut off by rate limits, SimplyLouie is $2/month for unlimited Claude API access. 7-day free trial, no commitment.
Top comments (1)
the step 1 ("understand before touching") is the whole ball game for legacy refactors. the failure mode i see most: people jump straight to asking claude code to refactor module X and get a confident-looking diff that subtly changes behavior in edge cases nobody documented.
the workflow i landed on is basically:
been collecting skills like this at tokrepo.com (open registry for claude code skills/slash commands/MCP configs) so people aren't re-inventing the same guardrails on every legacy project. this article is basically the manual version of what those skills automate — really nice to see the workflow written out explicitly.