Craft of code
Tiny refactor steps
One structural change, tests green, commit. Then change behavior in a different PR.
Safe refactoring is many tiny steps that are obviously behavior-preserving: rename, extract function, move file, inline a temp, replace a conditional with polymorphism. Martin Fowler's catalog is the menu. The discipline is the workflow, not the catalog.
Mixing "rename half the module + fix the discount bug + add a feature flag" in one PR makes review impossible and bisect useless. Authors who separate refactors from features get faster code review and safer rollbacks.
Concept
Stay green against characterization tests. If you discover a bug while tidying, stop. Merge the structural PR, then open a second PR that fails first on the bug.
Walking through it
1. Tests green.
2. One structural change.
3. Tests green.
4. Commit.
5. Repeat.
6. Then behavior change in a different PR.You need to add a new fee type to a 1,500-line switch. Preparatory refactor: extract each existing case into a strategy map keyed by fee code, with characterization tests for current fees. Behavior PR: add the new fee as one new entry. Reviewers see a clear addition instead of a conflict-prone edit in a monster function.
Hotfixes go the other way. Do not sneak renames into a pager fix. Keep the hotfix tiny and reversible; tidy when the pager is quiet.
When to tidy
Fowler's workflows pick when, not only how:
- Litter-pickup — a quick mess on the path; tidy it, keep the commit separate.
- Comprehension — you finally understand a block; put the names and dead-branch removal into the code before you leave.
- Preparatory — the feature is hard in the current shape; restructure first so the feature is a small diff.
- Planned — a campaign with milestones, tracked like a project.
Kent Beck's Tidy First? is the same idea: make the structural change that makes the feature easy, then make the easy change — in separate commits. Dedicated "cleanup weeks" fail when they lack a forcing product need. Prefer opportunistic tidying on the path of real work.
Trade-off
The anti-pattern is cleanliness with no link to delivery. The opposite is never tidying because "we only ship features," until every estimate quietly doubles.
Common mistakes
- One PR that both tidies and changes behavior. Reviewers miss the behavior; bisect attributes the bug to "cleanup."
- Tidying far from the next feature. Pick the module on the critical path.
- Open-ended cleanup sprints. Without a product need, they stall and burn trust.
- Sneaking refactors into a hotfix. Reversibility dies.
Check yourself
-
You found an off-by-one during a 40-file move. What do you ship first? The move, green against characterization tests. Then a second PR with an explicit failing test for the bug.
-
A 1,500-line
switchneeds a new fee. What is the preparatory step? Extract existing cases behind tests, then add the new fee as one entry.
Key takeaways
- One structural change, always green, then commit.
- Never mix refactor and behavior change in one PR.
- Use litter-pickup, comprehension, and preparatory workflows so tidying stays tied to delivery.
- Hotfixes stay tiny; tidy afterward.
Further learning
Fowler's "Workflows of Refactoring" names litter-pickup, comprehension, and preparatory work.
Next, scale the same discipline to systems in incremental migrations.
Related
Characterization tests
Pin what legacy code does today before you clean it, so a refactor cannot drift in silence.
Refactoring and legacy code
Change code you did not write without breaking it. Start here for characterization, seams, tiny steps, and incremental migrations.
Code review
Author small, reviewable PRs and give reviews that improve both the code and the author — design first, tone that teaches, turnaround that unblocks.