Refactor
Improve the structure, readability, or maintainability of existing code without changing its external behavior. Every step must keep the code in a working state.
If the refactoring scope is unclear or risky, clarify before starting.
1. Understand the Current State
Read Before Changing
- •Read all code in the refactoring scope — don't refactor code you haven't fully understood
- •Trace callers and consumers to understand the blast radius
- •Run existing tests to establish a passing baseline — if there are no tests, flag this as a risk
- •Identify the actual problem — "messy code" isn't specific enough; name the smell
Common Smells Worth Addressing
- •Duplication — Same logic repeated across multiple locations
- •Long functions — Doing too many things; hard to name, test, or reason about
- •Deep nesting — Excessive indentation from conditionals or callbacks
- •Primitive obsession — Passing raw strings/numbers where a domain type would be clearer
- •Feature envy — A function that uses more of another module's data than its own
- •Shotgun surgery — A single change requires edits across many unrelated files
- •Dead code — Unreachable code, unused exports, or commented-out blocks
What Not to Refactor
- •Code that works and isn't in the way of the current task
- •Code with no tests, unless you add tests first
- •Performance-sensitive code without profiling data
- •Code owned by another team or behind an API contract without coordination
2. Define the Target State
Before touching code, describe what "better" looks like:
- •What specific structural improvement are you making?
- •Which files and modules will be affected?
- •What will the public API look like after the refactoring?
- •What should remain unchanged?
If the refactoring is large, break it into phases with stable checkpoints between them.
3. Refactor Incrementally
Each step must leave the code in a working, testable state:
- •Add tests if missing — Cover the existing behavior before changing it
- •Make one structural change at a time — Rename, extract, inline, or move — don't combine
- •Run tests after each change — Catch regressions immediately, not at the end
- •Update callers and consumers — Don't leave broken references or stale imports
- •Remove dead code — Delete what's no longer used; don't comment it out
Safe Refactoring Moves
- •Extract — Pull a block into a named function, component, hook, or module
- •Inline — Replace a trivial abstraction with its implementation
- •Rename — Give a more accurate name to a variable, function, or file
- •Move — Relocate code to a more appropriate module or directory
- •Replace conditional with polymorphism — When a switch/if chain maps to distinct behaviors
- •Simplify signatures — Reduce parameters, use options objects, remove unused arguments
4. Verify
Before completing:
- • All existing tests still pass
- • External behavior is unchanged — same inputs produce same outputs
- • No unused imports, dead code, or orphaned files remain
- • Code is demonstrably simpler, not just different
- • Callers and consumers are updated consistently
Output Structure
Adapt depth to the refactoring size:
- •Problem — What smell or structural issue was addressed and why
- •Approach — The refactoring strategy chosen (extract, inline, reorganize, etc.)
- •Changes — Files modified and the nature of each change
- •Verification — Tests run, behavior confirmed unchanged