Refactoring
Guide safe, incremental code improvements that preserve behavior while enhancing design. Refactoring is disciplined — every change is small, tested, and reversible.
Core Rule
Never change behavior and structure in the same step. Either you're adding a feature (behavior change) or you're refactoring (structure change). Mixing them creates bugs that are hard to trace.
Workflow
Step 1: Ensure Test Coverage
Before refactoring anything, verify that tests exist for the behavior you're about to restructure. If tests are missing:
- Write characterization tests that document current behavior (even if the behavior has quirks)
- Run them and confirm they pass
- Only then begin refactoring
If the user resists writing tests first, explain: refactoring without tests is not refactoring, it's rewriting with hope.
Step 2: Identify Smells
Read the code and identify code smells. Prioritize by impact — don't try to fix everything at once. The most common smells, roughly ordered by severity:
- Long function (>30 lines) → Extract method
- Duplicated logic → Extract and share
- Deep nesting (>3 levels) → Early returns, extract method
- Long parameter list (>4 params) → Introduce parameter object
- Feature envy → Move method to the class it uses most
- Primitive obsession → Introduce domain types
- Shotgun surgery → Consolidate related logic
- Divergent change → Split class by responsibility
See references/transformations.md for the complete catalog of safe transformations.
Step 3: Plan the Sequence
Order refactoring steps so that each step:
- Is small enough to verify easily
- Keeps all tests green
- Makes the next step easier
A good sequence often looks like:
- Rename for clarity (cheapest, highest readability impact)
- Extract helpers to reduce function length
- Consolidate duplicates using the extracted helpers
- Move methods to better homes
- Simplify interfaces
Step 4: Execute — One Step at a Time
For each transformation:
- Explain what you're about to do and why
- Apply the single transformation
- Run tests and confirm green
- Show the diff to the user
If tests break, undo immediately. A failing test after refactoring means the transformation changed behavior — that's a bug, not a test to fix.
Step 5: Review the Result
After completing the planned sequence:
- Compare before/after: Is the intent clearer? Is the code simpler?
- Run the full test suite
- Check that no public API changed (unless that was the goal)
- Look for any new smells introduced by the refactoring
Principles Applied
- DRY: Eliminate duplication discovered during refactoring
- KISS: Every transformation should make the code simpler, not more abstract
- SRP: Each extracted function/class should have one reason to change
- YAGNI: Don't introduce abstractions for hypothetical future needs during refactoring
- Functional Independence: Refactoring should reduce coupling, not increase it
- Boy Scout Rule: Leave the code cleaner than you found it — every refactoring session should improve the surrounding code slightly, not just the target. Small cleanups (renaming, removing dead code) near the area you're working in compound over time.
- Kent Beck's 4 Rules of Simple Design: After refactoring, the code should: (1) pass all tests, (2) reveal intention clearly, (3) contain no duplication, (4) use the fewest classes and methods needed. Apply these as a checklist during Step 5 review.
Cross-Skill References
technical-debt-review— use to identify which areas are worth refactoring at a strategic level before startingdependency-impact-analysis— use before refactoring a shared component or public interface to understand blast radiuscode-slop-cleanup— most slop cleanup is a micro-refactor: use that skill for the pre-PR diff-scoped pass (removal-only, judged against file conventions); use this one when the cleanup turns structural (extract, move, re-abstract). They chain naturally: strip slop first, then refactor what remains