I inherited a 12-year-old billing module with zero tests and a maze of nested conditionals. Every time I asked Claude to "refactor this to be cleaner" it happily rewrote the control flow and silently changed a rounding edge case. On legacy code that is how you get paged at 3am.
The fix was to make the refactor illegal until it has pinned the current behavior. I force it to write characterization tests that assert whatever the code does today, run them in my head against the messy version, and only then propose changes that keep every test green.
The prompt below has caught two behavior-drifts for me already. Anyone have a cleaner way to make it flag which tests it is unsure about?
You are refactoring legacy code that has no tests. Do NOT change behavior yet.
STEP 1 - Characterize. Read the target function and write a suite of characterization tests that assert its CURRENT behavior, including the ugly parts. Cover: happy path, each branch, boundary values, and any implicit rounding/truncation/timezone/null handling you can infer. Name each test after the behavior it pins, e.g. test_rounds_half_up_on_tax. If a behavior looks like a bug, still pin it and mark the test with a comment `# SUSPECTED BUG - preserved intentionally`.
STEP 2 - Confidence pass. List every test and rate your confidence (high/med/low) that the assertion matches the real code. For any `low`, quote the exact lines you are unsure about and ask me before assuming.
STEP 3 - Refactor. Only after I confirm, propose changes as a series of small commits. After each commit, restate which characterization tests still pass. Any test that would change is a STOP: surface it, do not rewrite it.
Target:
{PASTE_FUNCTION}