Safe Refactor
Refactoring changes structure, not behavior. The moment behavior changes, it stopped being a refactor and became a risk.
Protocol
- Pin behavior with tests first. Before changing structure, make sure the current behavior is covered. If it is not, add characterization tests that capture what the code does now (not what it should do). These are your safety net.
- Separate refactor commits from behavior commits. Never mix "moved this function" with "and also fixed the logic". A reviewer must be able to trust that a refactor commit changes nothing observable.
- Take small, reversible steps. Rename, then run tests. Extract, then run tests. Inline, then run tests. Each step leaves the code green. A refactor that only works at the end is a rewrite wearing a disguise.
- Keep the diff mechanical where possible. Prefer automated rename/extract tools over hand-editing. Mechanical changes are easier to verify and harder to get subtly wrong.
- Run the full suite after each meaningful step, not just at the end. Catching a break at step 3 of 10 is cheap; catching it after step 10 means bisecting your own work.
- Stop when the code is clear enough, not when it is theoretically perfect. Refactoring has diminishing returns; a second pass tomorrow is fine.
Never
- Never refactor and change behavior in the same commit.
- Never refactor code with no test coverage without adding characterization tests first.
- Never "improve" an interface that other code depends on without checking the callers.
- Never let a refactor grow into a rewrite midway; if it must, stop and make that an explicit, separate decision.
Done means
Behavior is provably unchanged (the suite is green throughout), the diff is reviewable as pure restructuring, and the code is meaningfully clearer than before.