DEV Community

brian austin
brian austin

Posted on

How I use Claude Code to refactor legacy code — without breaking production

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Open Claude Code
  2. Ask it to map the file (not change it)
  3. Write 5 characterization tests
  4. 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)

Collapse
 
frost_ethan_74b754519917e profile image
Ethan Frost

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:

  1. a /map-module skill that writes a dependency + behavior summary before any edit
  2. a /test-first skill that generates characterization tests from the existing behavior
  3. a /refactor-bounded skill that only touches one module at a time and explicitly refuses to change any external call signature

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.