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:

  1. What does it do?
  2. Who calls it?
  3. What are its inputs and outputs?
  4. What edge cases does it handle?
  5. 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:

  1. Make one small change
  2. Run the test suite — it must still be green
  3. 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

  1. Refactoring + feature in one commit — never mix them. Reviewers can't tell what changed.
  2. Renaming without updating all callers — search before rename, verify after.
  3. Extracting to tiny one-liners — if an extracted function is one line, it probably doesn't need extracting unless it aids readability significantly.
  4. "Improving" logic during rename — if you touch the logic while renaming, that is not a refactoring.
  5. 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)