Claude CodeIntermediate

How to Refactor a Long Function Safely with Claude Code

Pin behavior with a test first, then have Claude break a long function into smaller pieces without changing what it does.

9 minIntermediate

Refactoring a long function is risky precisely because there is usually no test guarding its behavior. The safe order is: lock current behavior with a characterization test, refactor under that net, then confirm the test still passes. Claude Code can do all three steps, but you should drive the order so the safety net comes first.

What you need

  • Claude Code open in the repo
  • A working test runner
  • The long function you want to split, ideally under version control so you can diff

Step 1: Lock current behavior with a test

Before touching the function, ask Claude to write tests that capture what it does right now, including any quirks. These are your safety net. They should pass against the unchanged code.

Claude Code
You
Before any refactor, write characterization tests for buildInvoice in src/billing/invoice.ts that capture its current output for typical, empty, and discount inputs. Run them and confirm they pass against the existing code.
Agent
Added 4 tests in invoice.test.ts and ran them. All pass against the current implementation. Safe to refactor now.
Characterization tests come before the refactor, not after.

Step 2: Ask for a behavior-preserving refactor

Now request the refactor explicitly as behavior-preserving. Tell Claude what shape you want, for example extracting helpers, and tell it not to change the public signature or output.

Refactor buildInvoice into smaller private helpers
(line items, tax, discount) without changing its signature
or output. The characterization tests must still pass.
Show me the diff before applying.

Step 3: Read the diff before accepting

Review the proposed change as a diff. You are looking for one thing: that the extracted helpers add up to the original behavior and the entry function still returns the same thing. Reject any change that quietly alters logic.

invoice.ts - diff
Explorer
invoice.ts
invoice.test.ts
tax.ts
src/billing/invoice.ts
1export function buildInvoice(order) {
2 const lines = buildLineItems(order);
3 const subtotal = sum(lines);
4 const discount = applyDiscount(subtotal, order);
5 const tax = computeTax(subtotal - discount);
6 return { lines, subtotal, discount, tax,
7 total: subtotal - discount + tax };
8}

Step 4: Run the safety net and diff the output

Run the characterization tests. If they pass, the refactor preserved behavior. If a test fails, the refactor changed something, so revert or ask Claude to reconcile it against the locked behavior.

zsh - my-app
$npx vitest run src/billing/invoice.test.ts
Tests 4 passed (4)
behavior preserved, refactor is safe to keep
$
Always ask for the diff first
Telling Claude to show the diff before applying lets you catch a logic change before it lands. Reviewing after the fact is harder because the original is already gone.

Result: one 80-line function becomes a short entry point plus three named helpers, and the same four tests still pass, proving the output never changed.

Watch related tutorials

Tags
#refactor#testing#safety