Library · Summary & review

Refactoring

By Martin Fowler. What I take from the 2nd Edition, and why your IDE has a Refactor menu because of this book.

FR EN
Refactoring book cover, 2nd Edition, Martin Fowler

Refactoring

Refactoring: Improving the Design of Existing Code, 2nd Edition

8.2 /10

« Seventy pages of perfect teaching, three hundred of dictionary: you keep both for life. »

  • AuthorMartin Fowler · martinfowler.com
  • OriginalAddison-Wesley, 2018 · 448 pages
  • Edition2nd edition (2018), examples in JavaScript
  • This page~10 min read
Book rating across 5 dimensionsIdeas9/10Practical9/10Readability7/10Aged well8/10Examples8/10

The trade's core move, finally named: ~60 safe transformations, tiny steps, tests at every step.

Why this book

The first edition (1999) gave a name to the most everyday move in the trade. Before it, "improving the code" was a vague intention; after it, there were some sixty named moves, each with its safe step-by-step mechanics. If your editor has a Refactor menu, it is because of this book.

This second edition (2018) rewrites everything in JavaScript and swaps the running example: the video rental store of 1999 becomes a theatre troupe's billing program, because, as Fowler puts it, today you'd have to start by explaining "What's a video rental store?"

The ideas that stick

Four chapters of lessons, then a catalog of about sixty refactorings. Here is what actually stays.

1Refactoring has a strict definition

The word gets used for any cleanup; the book is precise. The definition: "a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior" (ch. 2). Behavior unchanged, that is the whole contract. The instant diagnostic: "If someone says their code was broken for a couple of days while they are refactoring, you can be pretty sure they were not refactoring."

The discipline comes with Kent Beck's metaphor of the two hats: you either add functionality or you refactor, never both at once. You swap hats often, sometimes every few minutes, but always knowingly. Mixing them is how a "quick cleanup" turns into a week-long swamp.

"Without changing observable behavior" is the key phrase. Refactoring never touches what the code returns, only its shape:

// before: it works, but it is unreadable
function p(d) { return d.q * d.u * (d.q > 10 ? 0.9 : 1) }

// after: EXACTLY the same result, clearer structure
function totalPrice(order) {
  const discount = order.quantity > 10 ? 0.9 : 1
  return order.quantity * order.unitPrice * discount
}

Both functions return the same number for the same inputs. That is the contract: a test that passed before still passes after. If anything in the result changes, it is no longer refactoring, it is a modification.

🎨 Illustration to generate : copy this prompt into ChatGPT :

Flat illustration, warm ivory background (#faf7f2), muted green and terracotta palette, no text, minimalist editorial style, a developer at a desk mid-swap between two hats, one muted green and one terracotta, one hat on the head and the other held in hand, a code editor glowing softly in front, deadpan humor, 3:2 aspect ratio

Planned caption: "Add a feature, or refactor. One hat at a time."

2Everything is in the rhythm: tiny steps, never broken

Chapter 1 is seventy pages of live demonstration. Starting from one 44-line statement() function that bills a theatre troupe, Fowler chains ten named refactorings, and after every single move: compile, test, commit. Not once does the code stop working.

He names this rhythm himself as the most important lesson of the book: "the key to effective refactoring is recognizing that you go faster when you take tiny steps, the code is never broken, and you can compose those small steps into substantial changes" (ch. 1). He learned it from Kent Beck "in a hotel room in Detroit two decades ago". And he confesses: "I don't always take quite as short steps as these—but whenever things get difficult, my first reaction is to take shorter steps."

3It is purely economic, not moral

Fowler kills the clean-conscience argument: "The point of refactoring isn't to show how sparkly a code base is—it is purely economic" (ch. 2). You refactor to ship faster. His central argument, the Design Stamina Hypothesis, says good internal design sustains the team's speed over time, and he is honest enough to label it: "I can't prove that this is the case, which is why I refer to it as a hypothesis."

The most profitable form is preparatory refactoring: "The best time to refactor is just before I need to add a new feature." Kent Beck compressed it into a tweet the book quotes: "for each desired change, make the change easy (warning: this may be hard), then make the easy change." Jessica Kerr's image: drive 20 miles north to the highway, then go 100 miles east at three times the speed.

Concretely: you are asked to add the "express" carrier. The current code makes that change painful.

// the code as is: adding a case = growing the if/else, fragile
function fee(carrier) {
  if (carrier === 'standard') return 4.99
  if (carrier === 'pickup')   return 0
}

// 1) make the change EASY (the prep refactor): a table
const RATES = { standard: 4.99, pickup: 0 }
function fee(carrier) { return RATES[carrier] ?? 4.99 }

// 2) make the easy change: one line, no logic touched
RATES.express = 9.99

Without the prep refactor, adding "express" meant going into the if/else and risking the existing cases. After it, it is one line of data. That is the whole point of Beck's tweet: you paid for a small cleanup first so the real request became trivial.

And the counterpart: messy code you never need to modify is code you leave alone. "If I run across code that is a mess, but I don't need to modify it, then I don't need to refactor it."

4Smells replace metrics

When should you refactor? Chapter 3, co-written with Kent Beck, opens on his grandmother's rule: "If it stinks, change it." The book explicitly refuses thresholds: "no set of metrics rivals informed human intuition." For long functions, the real criterion is "not function length but the semantic distance between what the method does and how it does it."

Twenty-four smells are cataloged. The ones I meet every week:

  • Feature Envy: a function spends its time with another module's data;
  • Shotgun Surgery: one logical change means touching ten files;
  • Data Clumps: the same 3-4 values always travel together, an object waiting to be born;
  • Primitive Obsession: strings and ints standing in for business concepts;
  • Mutable Data: "One of the biggest sources of problems in software is mutable data";
  • Comments: used as deodorant; the urge to comment a block means extract a function instead.

One smell in the flesh, Data Clumps: as soon as the same values travel glued together everywhere, an object is asking to be born.

// smell: city + zip + street always travel together, in every signature
function deliver(city, zip, street) { ... }
function invoice(city, zip, street) { ... }

// after: those 3 values WERE a concept, give it a name
class Address { city; zip; street }
function deliver(address) { ... }
function invoice(address) { ... }

Fowler's personal threshold, for the record: "any function with more than half-a-dozen lines of code starts to smell."

🎨 Illustration to generate : copy this prompt into ChatGPT :

Flat illustration, warm ivory background (#faf7f2), muted green and terracotta palette, no text, minimalist editorial style, a kind elderly grandmother with glasses holding a sheet of source code at arm's length like a smelly dirty diaper, clothespin on her nose, small stink wavy lines rising from the paper, deadpan humor, 3:2 aspect ratio

Planned caption: "Grandma Beck's rule: if it stinks, change it."

5No refactoring without a net

The condition for all of the above: "Refactoring requires tests. If you want to refactor, you have to write tests" (ch. 4). Fowler discovered it empirically in 1992: making his tests self-checking nearly eliminated his debugging time. The distinction is concrete:

// ✗ not self-checking: the machine prints, YOU are the judge
console.log(totalPrice(order))   // "53.91" … is that right? you re-read it

// ✓ self-checking: the test itself says PASS or FAIL
expect(totalPrice(order)).toBe(53.91)

With the first version, refactoring 200 functions would mean eyeballing 200 console outputs. With the second, you run the suite and the red jumps out. That is what makes idea 2's "tiny steps" possible: without a self-checking net, you don't dare. "A suite of tests is a powerful bug detector that decapitates the time it takes to find bugs."

The practical rules have aged remarkably well: a fresh fixture for every test (never shared state between tests), temporarily inject a fault to check that a test knows how to fail, and measure sufficiency by confidence, not coverage: if someone broke the code, would some test catch it?

6Conditionals: guards, flags, polymorphism

The catalog chapter that pays off daily is the one on conditional logic. First move: guard clauses. When one branch is the normal case and the others are exceptions, the exceptions leave early and the normal path reads flat:

// before: the normal case is buried three levels deep
if (!isDead) {
  if (!isSeparated) {
    if (!isRetired) { result = normalPayAmount(); }
  }
}

// after: guard clauses, the normal case reads flat
if (isDead)      return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired)   return retiredAmount();
return normalPayAmount();

Along the way Fowler dismantles the single-exit-point rule: "one exit point is really not a useful rule. Clarity is the key principle." Second move: kill flag arguments, because "in a function call, I can't figure out what true means"; two well-named functions beat one boolean. Third: Replace Conditional with Polymorphism, reserved for switches repeated on the same type. With the anti-dogma caveat: "Most of my conditional logic uses basic conditional statements—if/else and switch/case."

7Every refactoring has an inverse

The catalog's quiet meta-lesson: the moves come in pairs, and the way back always exists. The flagship pair, Extract Function ↔ Inline Function, is the same code read both ways:

// Extract Function → : pull out a named piece (clarifies a big function)
function total(o) { return subtotal(o) + vat(o) }
function vat(o)   { return subtotal(o) * 0.2 }

// ← Inline Function: reabsorb it (when the name no longer earns its keep)
function total(o) { return subtotal(o) * 1.2 }

Neither direction is "the right one": extracting clarifies when the piece has a business meaning, reabsorbing simplifies when the name is a pointless detour. The right direction depends on this week's context. The other pairs follow the same logic: Hide Delegate and Remove Middle Man, Replace Parameter with Query and its inverse, Pull Up and Push Down.

That is what frees you from dogma. On the right amount of encapsulation: "it's hard to figure out what the right amount of hiding is. Fortunately, with Hide Delegate and Remove Middle Man, it doesn't matter so much. I can adjust my code as time goes on." Even the Law of Demeter gets the treatment: Fowler would like it a lot more "if it were called the Occasionally Useful Suggestion of Demeter."

8Inheritance is a card you only get to play once

Where The Pragmatic Programmer says "stop", Fowler explains how to live with it. His position (ch. 12): "I use inheritance frequently, partly because I always know I can use Replace Subclass with Delegate should I need to change it later." He rewrites the famous slogan his own way: "Favor a judicious mixture of composition and inheritance over either alone—but I fear that is not as catchy."

The mechanism behind the judgment: inheritance handles exactly one axis of variation. As long as there is only one, it is perfect; the day a second appears, the tree explodes combinatorially.

// 1 axis (the type): inheritance works great
class Booking {}
class PremiumBooking extends Booking {}

// 2nd axis (refundable or not): now you need ONE class per combination
class RefundablePremiumBooking extends ... ?  // and so on: 2×2, then 2×3…

// delegation: the booking COMPOSES two independent axes
class Booking {
  constructor(pricing, refundPolicy) { ... }  // 2 parts, no more tree
}

That is the exact moment Fowler applies Replace Subclass with Delegate, and the catalog gives the step-by-step path. The historical counter-example he cites: making Stack a subclass of List, whose methods you inherit but must then refuse. Delegation is the "favor composition" of the Design Patterns note, seen from the refactoring side: you don't get there in one shot, you migrate when the 2nd axis appears.

Three things I didn't know

My take, honestly

Chapter 1 is the best code lesson I have read: seventy pages where you watch someone work, every move explained, the code never broken. And Fowler has a rare kind of honesty. His central argument is labeled an unproven hypothesis, and his advice is full of "I'm not 100 percent pure on this".

The limit: half the book is a dictionary. Nobody reads chapters 7 to 12 cover to cover; you look things up. The JavaScript examples were the right call in 2018 and already feel dated on the tooling side (Babel, Mocha). And the "don't tell your manager you're refactoring" advice is flagged as controversial by Fowler himself: it is.

But that dictionary changed the trade: your editor's Refactor menu exists because of this book. Read chapters 1 to 4 like a novel, keep the rest within reach during code review. Nine developers out of ten should read the first hundred pages; the catalog will wait for them on the shelf.

Odilon

Still relevant in 2026?

More than in 2018. AI assistants produce working-but-clumsy code at high speed, and this book is the grid for judging it: the smells catalog reads like a checklist for reviewing generated code, and "first refactor to make the change easy, then make the easy change" is exactly the right workflow with an assistant. Tools and LLMs automate the mechanics of each refactoring; knowing when and what remains judgment, and that is precisely what the book teaches. What has aged: the 2018 JavaScript tooling, and the hard line against feature branches (short PRs won without killing refactoring).

Who is it for?

Read it if

  • You maintain legacy code and "refactoring" currently means a scary week-long overhaul
  • You want to name the moves in code review instead of saying "clean this up a bit"
  • You read Clean Code and want the how-to that goes with the why
  • You review AI-generated code: the smells are your checklist

Skip it if

  • You expect a cover-to-cover read: half the book is a reference to consult
  • You don't write tests yet: start there, refactoring without a net is gambling
  • You are looking for macro architecture: this is micro-design, function by function

Going further

The safety net is built in my testing course, and inheritance versus composition is covered in the OOP course. In the library, Clean Code is the why to this how, The Pragmatic Programmer shares the inheritance-tax fight, and TDD by Example is where the rhythm of tiny steps comes from.

Comments (0)

Browse the whole library

More book notes coming: one book at a time, the marrow only.