AI & Automation

Refactoring Legacy Code with AI: A Practical Playbook

MatterAI
MatterAI
13 min read·

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

Get started free - https://app.matterai.so


Follow us on X · LinkedIn · GitHub

Share this Guide:

Ship Faster. Ship Safer.

Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.

No credit card requiredSOC 2 Type IISetup in 2 min