Refactoring Is Behavior Preservation
What This Concept Is
Refactoring is not "cleaning up code." It is a specific, disciplined activity.
Fowler's definition (noun):
Refactoring: a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.
Two parts matter:
- internal structure changes - names, shapes, boundaries, control flow
- observable behavior does not change - what callers can see stays the same
The verb form is: apply a series of small behavior-preserving steps. At every step, the program still works. If your code was broken for two days while you were "refactoring," you were not refactoring.
Why It Matters Here
Later moves in this module (Extract Function, Move Field, Split Phase, Replace Conditional with Polymorphism) are only valid if you can prove behavior is preserved. The proof is a green test suite after each step.
Calling arbitrary cleanup "refactoring" hides real risk. It also lets "I refactored for two weeks and broke production" happen and blame the word instead of the discipline.
Concrete Example
Before:
function priceOrder(order) {
const basePrice = order.quantity * order.itemPrice;
const discount = Math.max(order.quantity - 500, 0) * order.itemPrice * 0.05;
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - discount + shipping;
}
After Extract Function, three times:
function priceOrder(order) {
return applyShipping(basePriceOf(order), discountOn(order));
}
function basePriceOf(order) { return order.quantity * order.itemPrice; }
function discountOn(order) { return Math.max(order.quantity - 500, 0) * order.itemPrice * 0.05; }
function applyShipping(base, discount) { return base - discount + Math.min(base * 0.1, 100); }
Same inputs, same outputs, same error behavior. The call stack changed; that is not observable to a caller who sees only the return value.
Common Confusion / Misconception
"I renamed a field and fixed the off-by-one bug in the same commit." That is not a refactor. Observable behavior changed (the bug fix). If the rename later introduces a regression, you cannot bisect to the rename commit because it contains two changes.
Also: performance changes are usually allowed. Extract Function adds a call; that is internal. Observable behavior is what callers contract on, not CPU cycles, unless performance is part of the contract.
How To Use It
Before you touch a line, ask:
- Can I state the observable behavior this code has right now?
- Do I have a test that will fail if I break it?
- Is my change going to alter what callers see?
If (3) is yes, stop: you are doing a feature change, not a refactor. Switch hats (see concept 02).
Check Yourself
- Is "rename a public API method" a refactor? What must be true for it to be?
- If Extract Function changes the call stack in a profiler, has observable behavior changed?
- Your coworker says "I refactored the caching layer for three days." What single question tells you whether they actually did?
Mini Drill or Application
Pick a function in your code that is at least 30 lines long. Write the observable behavior as three sentences, covering normal path, one edge case, and one error case. Now apply Extract Function once. Confirm all three sentences remain exactly true after the move by running your tests. If they do, you have executed a refactor. If a test failed, you changed behavior.
Video and Lecture References
- Primary lecture: Martin Fowler -- Workflows of Refactoring (20-minute infodeck)
- Visual supplement: Refactoring: Improving the Design of Existing Code -- overview (reference page)
Article References
- Fowler: Refactoring Malapropism - the cost of using the word loosely
- Refactoring.guru: What is refactoring? - alternate short explanation
External Exercises
- Emily Bache -- Gilded Rose kata - classic first refactor-with-tests exercise
- Refactoring.com: online catalog - index to go deeper into any named move
Depth Path
- Read This Only If Stuck - Fowler chunks 013, 014 for the defining chapter
- Optional deep dive: chunk 017-019 on problems with refactoring (when it hurts)