Skip to main content

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 relative to a stated behavior-preservation contract. A green suite is bounded evidence, not a proof. State the input domain and observable boundary, then combine tests with a transformation argument.

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); }

For plain records with stable numeric fields, the arithmetic and evaluation order agree. This is not an all-JavaScript-objects theorem: proxies, getters, monkey-patched built-ins and stack inspection need additional contracts. Document excluded observations. Performance changes must remain within agreed resource limits.

Common Confusion / Misconception​

"I renamed a field and fixed an off-by-one bug." This combines a structural change and a repair. Bisect can identify the mixed commit; it cannot determine which edit inside caused the regression. Separate the evidence and preserve useful checkpoints.

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:

  1. Can I state the observable behavior this code has right now?
  2. Do I have a test that will fail if I break it?
  3. 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​

  1. Is "rename a public API method" a refactor? What must be true for it to be?
  2. If Extract Function changes the call stack in a profiler, has observable behavior changed?
  3. 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, the tested observations agree; still inspect untested branches and effects. A failure is evidence to investigate: distinguish a semantic regression from a brittle structural assertion or unstable fixture. Passing those cases does not establish all-input equivalence.

Engineering Extension: Define the observation boundary​

Compare return values, exception classes, mutation, ordered collaborator calls and output. Specify whether diagnostic stack traces, private fields and timing are contractual. A green suite with no equality-deadline case cannot detect changing < to <=. Try that mutation on the invoice fixture and explain why a preservation claim needs a domain as well as examples.

Use the behavior contracts, labs and practice feedback to test the claim.

Read This Only If Stuck​

Locate this move through the Refactoring book index. The selective reading guide gives exact consulted local locators and primary documentation. Index links are not exact chapter links.