Refactoring Legacy Code with AI: A Practical Playbook
Refactoring Legacy Code with AI: A Practical Playbook
Legacy code is where AI coding agents deliver the most value and cause the most damage. The value: agents can read and rewrite code humans have forgotten exists. The damage: a confident agent can silently change behavior across a codebase no one fully understands. The difference between the two outcomes is process. This playbook is that process.
The Core Principle: Behavior First
Never refactor code you cannot verify. Before any AI touches legacy code, you need a safety net that captures current behavior. The net is characterization tests: tests that record what the code does today, not what it should do.
# test_legacy_characterization.py
import json
from legacy import process_order
def test_process_order_current_behavior():
# Captured from production fixtures — documents TODAY's behavior
with open("fixtures/orders.json") as f:
orders = json.load(f)
for order in orders:
result = process_order(order)
assert result == order["expected_output"] # recorded, not ideal
Characterization tests are allowed to encode bugs. Their job is not to be right; it is to be stable. If a refactor changes behavior, the test fails, and you decide consciously whether the change is a fix or a regression.
The Refactoring Loop
Step 1: Map the blast radius
Before prompting an agent, know what the code touches:
# Find all callers of the function you plan to refactor
rg -n "process_order\(" --type py
Step 2: Capture behavior with characterization tests
Generate fixtures from real inputs and record outputs. For a function with many callers, capture at the boundary:
def capture_fixtures(func, inputs, output_path):
fixtures = []
for i, args in enumerate(inputs):
fixtures.append({
"input": args,
"output": func(*args),
})
with open(output_path, "w") as f:
json.dump(fixtures, f, indent=2)
Step 3: Refactor in small slices with the agent
Give the agent one bounded task at a time:
Refactor process_order in legacy.py:
- Extract the discount calculation into a pure function compute_discount(order).
- Keep the public signature of process_order unchanged.
- Do not change behavior.
Verification: run `pytest test_legacy_characterization.py` — all tests must pass.
Small slices matter. A prompt that says "modernize this 2,000-line module" produces a rewrite you cannot review. A prompt that says "extract this one calculation" produces a diff you can read in minutes.
Step 4: Verify with the characterization net
Run the full test suite after every slice. The agent's claim of success is not evidence; the test output is.
Step 5: Review the diff like a human
AI refactors need human review more than AI-written new code, because the risk is silent behavior change. Review for:
- Signature changes that break callers the agent did not see.
- Error handling changes: the agent may "simplify" away a try/except that was load-bearing.
- Ordering changes: dict iteration, sort stability, and side-effect order are behavior.
- Edge case removal: the agent may delete a branch it judged dead.
The Strangler Pattern for Big Migrations
For a whole module or service, do not rewrite in place. Strangle it: build the new implementation alongside, route traffic gradually, and delete the old code only when the new one is proven.
legacy_service ──► [feature flag] ──► new_service (agent-written)
│
└──► legacy_service (until flag = 100%)
def process_order(order):
if feature_flag("new_order_pipeline"):
return new_pipeline.process(order) # agent-written, tested
return legacy_process(order) # old path, still live
Each percentage point of traffic is a test. When the new path has handled real traffic without incident, flip the flag and delete the legacy path.
Agent-Assisted Migration Patterns
Type annotation migration
Add type annotations to payment.py. Use the existing runtime behavior to infer types.
Do not change any logic. Run `mypy payment.py` and fix only type errors.
Test framework migration
Migrate tests in tests/ from unittest to pytest.
Preserve test names and assertions exactly. Run the full suite.
Dependency modernization
Replace the deprecated requests calls in api_client.py with httpx.
Keep the same method signatures and error behavior. Run the suite.
Each of these is mechanical enough for an agent and verifiable enough for a test suite.
What Agents Are Bad At (Do These Yourself)
- Understanding business intent: the agent knows the code, not why the code exists. You decide what behavior is a bug worth fixing.
- Cross-module impact: agents miss callers in other services, configs, and data pipelines. You map the blast radius.
- Performance-sensitive rewrites: an agent's "cleaner" version can be 10x slower. Benchmark before and after.
- Security-sensitive code: auth, crypto, and validation rewrites need human review, always.
Implementation Checklist
- Map all callers before touching anything
- Capture current behavior with characterization tests
- Refactor in small, reviewable slices
- Give the agent one bounded task per prompt
- Require test output as proof, not claims
- Review diffs for signature, error-handling, and ordering changes
- Use the strangler pattern for whole-module migrations
- Benchmark and security-review agent rewrites yourself
MatterAI builds frontier AI infrastructure for engineering teams — from inference-optimized models to autonomous coding agents and agentic code reviews.
Explore what we're building:
- Orbital IDE — Autonomous AI coding agent with background agents and deep codebase memory
- AI Code Reviews — Agentic pre-commit reviews across GitHub, GitLab, and Bitbucket
- Axon Models — Frontier-grade reasoning models at 70% lower inference cost
Share this Guide:
More Guides
Local LLMs in Your IDE: Connecting Ollama to Coding Agents and Autocomplete
Wire local models into VS Code, JetBrains, Cline, Continue, and Aider via the OpenAI-compatible API. Covers model routing, context budgets, tool calling with small models, and when a local model is the right choice for the job.
15 min readBuilding a Self-Hosted AI Stack: Ollama, Open WebUI, and Local RAG
Stand up a fully self-hosted AI stack on a single machine: Ollama for inference, Open WebUI as the chat interface, local embeddings for RAG, and a reverse proxy for secure access. No cloud dependency, no data leaving your network.
17 min readTop 5 Open-Source Coding Models to Run on Your Mac (2026)
The best local coding models for Apple Silicon in 2026, ranked by quality per gigabyte of unified memory. Covers qwen3-coder, devstral, gpt-oss, and more with real pull tags, sizes, and context windows.
14 min readRunning LLMs Locally: GGUF, Quantization, and Memory Planning
Learn the GGUF format, the quantization ladder from Q2 to FP16, and the exact memory math for running models on Apple Silicon and NVIDIA GPUs. Includes Ollama and llama.cpp tuning for KV cache and context.
15 min readOllama vs vLLM vs llama.cpp: Choosing the Right Local LLM Runtime
Compare the three dominant local LLM runtimes on architecture, throughput, hardware, and deployment context. Includes benchmark data, a decision framework, and a migration path from Ollama to vLLM.
16 min readContinue Reading
Local LLMs in Your IDE: Connecting Ollama to Coding Agents and Autocomplete
Wire local models into VS Code, JetBrains, Cline, Continue, and Aider via the OpenAI-compatible API. Covers model routing, context budgets, tool calling with small models, and when a local model is the right choice for the job.
15 min readBuilding a Self-Hosted AI Stack: Ollama, Open WebUI, and Local RAG
Stand up a fully self-hosted AI stack on a single machine: Ollama for inference, Open WebUI as the chat interface, local embeddings for RAG, and a reverse proxy for secure access. No cloud dependency, no data leaving your network.
17 min readTop 5 Open-Source Coding Models to Run on Your Mac (2026)
The best local coding models for Apple Silicon in 2026, ranked by quality per gigabyte of unified memory. Covers qwen3-coder, devstral, gpt-oss, and more with real pull tags, sizes, and context windows.
14 min readShip Faster. Ship Safer.
Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.
