Refactoring
You are about to refactor: $ARGUMENTS
Refactoring is disciplined, behaviour-preserving code improvement. The rules are strict because mistakes here are invisible by definition — a refactoring that changes behaviour is a bug, not an improvement.
The Prime Directive
A refactoring that changes observable behaviour is a bug.
Before touching anything, you must have a test suite that covers the code you are about to change. If tests do not exist, write characterisation tests first. No tests = no refactoring.
Before You Start
# Run the test suite right now — all tests must be green before you begin
# substitute your test runner
npm test
./vendor/bin/phpunit
pytest
go test ./...
# Confirm what you're about to change
git diff # should be empty — start from a clean working tree
git status # no uncommitted changes
If tests are failing before you start, stop. Fix them or document them. Do not refactor broken code.
Step 1 — Understand Before You Change
Read the code you intend to refactor completely before modifying any of it:
- What does it do?
- Who calls it?
- What are its inputs and outputs?
- What edge cases does it handle?
- What would break if you changed it subtly?
# Find all callers of the thing you're refactoring
grep -rn "FunctionName\|ClassName\|method_name" \
--include="*.{php,js,ts,py,go,rb,java,cs}" . | grep -v node_modules | grep -v vendor | grep -v test
Step 2 — Identify the Refactoring Type
Apply only one refactoring type per commit. Never combine a refactoring with a feature change.
Extract Function / Method
When: A block of code has a clear purpose that can be named. Rule: If you can name it clearly, extract it. If you can't name it clearly, don't.
BEFORE: 50-line function doing three different things
AFTER: 3 well-named functions of 15 lines each, one orchestrating function
Inline Function
When: A function's body is as clear as its name, or it's only called once and adds no clarity.
Rename
When: The current name is unclear, misleading, or inconsistent with what it does.
Rule: Names must describe what, not how. processData() is bad. buildInvoiceLineItems() is good.
Extract Class
When: A class has more than one reason to change, or groups of methods form a separate concept.
Move Method / Function
When: A method uses data from a different class more than its own class's data.
Replace Conditional with Polymorphism
When: A switch/if-else checks a type and dispatches different behaviour.
Remove Dead Code
When: Code that is never reached, commented out, or disabled. Tool:
grep -rn "TODO\|FIXME\|HACK\|XXX\|DEAD\|UNUSED" --include="*.{php,js,ts,py,go,rb}" . | grep -v node_modules | grep -v vendor
Replace Magic Number/String with Named Constant
// Before
if ($status === 3) { ... }
// After
if ($status === OrderStatus::SHIPPED) { ... }
Introduce Parameter Object
When: A function takes 4+ parameters that always travel together.
Decompose Conditional
When: Complex boolean expressions are hard to read.
// Before
if ($user->age >= 18 && $user->is_verified && !$user->is_banned && $subscription->isActive()) {
// After
if ($user->isEligibleForPremiumContent() && $subscription->isActive()) {
Step 3 — Refactor in Small Steps
Each step:
- Make one small change
- Run the test suite — it must still be green
- Commit with message:
refactor: [what you extracted/renamed/moved]
Never refactor in one massive commit. A series of small commits is easier to review and easier to revert.
# After each small step:
[run test suite]
git add -p # stage only the refactoring changes
git commit -m "refactor: extract processPayment() from checkout handler"
Step 4 — Verify Zero Behaviour Change
After the refactoring is complete, run the full test suite one more time. Then:
# Verify the public interface is unchanged
git diff HEAD~N HEAD -- [public API files / routes / CLI interface]
# Check that outputs are identical on known inputs
# Run the same representative operations before and after and compare
Refactoring Anti-Patterns to Avoid
- Refactoring + feature in one commit — never mix them. Reviewers can't tell what changed.
- Renaming without updating all callers — search before rename, verify after.
- Extracting to tiny one-liners — if an extracted function is one line, it probably doesn't need extracting unless it aids readability significantly.
- "Improving" logic during rename — if you touch the logic while renaming, that is not a refactoring.
- Extracting without characterisation tests — if tests don't cover what you're extracting, you don't know if your extraction changed behaviour.
Measurement
Before and after a significant refactoring, record:
| Metric | Before | After |
|---|---|---|
| Lines in largest function | ||
| Lines in largest class | ||
| Number of parameters in worst function | ||
| Test suite time | ||
| Cyclomatic complexity (if tool available) |