Refactor Without Breaking It
Improve code structure while proving behaviour is unchanged. Tests the discipline of characterising existing behaviour before changing anything — the part everyone skips.
Why this one
A refactor is a change that preserves behaviour. Without tests you are not refactoring, you are rewriting and hoping. Generated refactors are especially prone to quietly "fixing" behaviour you depended on.
Steps
Find genuinely tangled code
Your own is best. Failing that, ask for a function with three responsibilities and nested conditionals — something with enough structure to get wrong.
Characterise the current behaviour first
Write tests that document what the code does today, including the parts that look like bugs. Those tests are the contract. Without them there is no way to tell a refactor from a rewrite.
Ask for the refactor with a hard constraint
Say explicitly that existing behaviour must be preserved exactly, bugs included, and that any behaviour change must be listed separately rather than applied.
Run the tests and read the diff
Failing tests mean behaviour changed. Then read the diff line by line — some behaviour changes pass tests because the tests were incomplete, and only reading catches those.
Decide about the bugs separately
If the refactor "fixed" something, revert that part and fix it as its own change. Mixing a refactor with a behaviour change makes both impossible to review and impossible to revert.
You should end up with
A refactored function, passing characterisation tests, and any behaviour changes separated out.
Done when
- Tests were written before the refactor
- All characterisation tests still pass
- Behaviour changes were separated from structural ones
If you want to go further
- Do it a second time on the same code with a different structural goal and compare