AI Study Notebook AI-generated
Study Guide: Refactoring: Improving the Design of Existing Code
Martin Fowler
By Best Books
This AI-generated study guide is a reading aid. The source-backed recommendation record and evidence for this book live on the book page.
On this page
Refactoring: Improving the Design of Existing Code — Chapter-by-Chapter Outline
Author: Martin Fowler (with contributions by Kent Beck) First published: 1999 (1st edition); 2018 (2nd edition) Edition covered: 2nd edition (November 2018, Addison-Wesley Professional). The 2nd edition drops the 1st edition's final four chapters (Refactoring, Reuse, and Reality; Refactoring Tools; Putting It All Together; and the four "Big Refactorings") in favor of a restructured, 12-chapter format. Examples shift from Java to JavaScript, the catalog is reorganized by theme rather than technique type, and 17 new refactorings are added while 19 from the 1st edition are removed. The 1st edition's 15-chapter structure is not covered here.
Central thesis
Code is written once but read and modified many times. As a codebase grows, the internal structure tends to decay: functions accumulate responsibilities, data structures become tangled, conditionals proliferate, and the intent of the code becomes harder to read. This decay — what Fowler calls cruft — slows down every subsequent change, making bugs easier to introduce and harder to find.
Refactoring is the disciplined practice of restructuring existing code without changing its observable behavior. Applied in small, safe steps — each step verified by automated tests — refactoring lets programmers continuously improve the internal design of a system while leaving its external behavior intact. The result is code that is cheaper to understand, cheaper to change, and cheaper to extend.
The book's organizing claim is that refactoring is not a separate activity to schedule alongside feature development; it is an integral part of programming itself. Developers toggle between two modes — adding new functionality and improving the structure that supports it — and the fluency with which they do so determines the long-term health of their codebase.
If you have to add a feature to a program but the code is not structured in a convenient way, first refactor the program to make it easy to add the feature, then add the feature.
Chapter 1 — Refactoring: A First Example
Central question
How does refactoring actually work in practice — what does it look like step by step, and why does it matter?
Main argument
The starting program
Fowler opens with a deliberately simple program: a theater billing system that calculates the total charge and volume credits for a customer's set of performances, printing them as a plain-text statement. The initial code works correctly, but a single statement() function handles everything — price calculation, credit computation, and output formatting — crammed into nested conditionals. The program is comprehensible now, but adding new output formats (HTML) or new performance types would require significant duplication and risk.
The first step: build tests before touching anything
Before any restructuring, Fowler writes a test suite that captures the current output for known inputs. This test suite is the safety net: if a refactoring step accidentally changes behavior, a test will fail immediately. The rule is absolute — no refactoring without tests.
Decomposing the statement function: Extract Function
The switch statement that computes the charge for each performance type is extracted into its own function, amountFor(). The extraction requires identifying which local variables are used inside the candidate fragment and whether any of them are modified — modified variables must become return values. After extraction, Fowler renames parameters to make intent clearer (perf → aPerf, play → aPlay), demonstrating that renaming is itself a refactoring.
Removing temporary variables: Replace Temp with Query
The play variable is computed once and reused inside the loop. Fowler replaces it with a direct function call (playFor(aPerformance)). Temporary variables that merely hold intermediate results encourage long functions; eliminating them in favor of queries makes each fragment more independently intelligible. The minor performance cost is negligible for business logic, and performance optimization can come later once structure is sound.
Splitting phases: Separate Calculation from Formatting
The statement() function conflates two responsibilities: computing the data and rendering it as text. Fowler introduces a renderPlainText() function that receives a structured data object, and a separate pass that builds that data. This Split Phase refactoring makes it trivial to add an HTML-rendering path: create renderHtml() that consumes the same data object, with no duplication of the calculation logic.
Replacing conditional logic with polymorphism
The nested switch on performance type appears repeatedly. Fowler introduces a PerformanceCalculator class and subclasses (TragedyCalculator, ComedyCalculator), each overriding amount and volumeCredits. A factory function returns the right subclass for each performance type. The conditionals evaporate — new performance types are added by creating a new subclass, not by finding and editing every switch block.
Key ideas
- Refactoring is most valuable when the codebase is about to grow; the first move before adding a feature is often to restructure the code so the feature fits cleanly.
- Tests are a prerequisite, not a nice-to-have; without them refactoring is just guessing.
- Small, named extractions accumulate: each one makes the next one easier to see.
- Temporary variables discourage decomposition; eliminating them in favor of queries increases modularity.
- The progression from conditional logic to polymorphism is a recurring refactoring theme: each repetition of the same
switchis a signal that a polymorphic design would be cleaner. - The example ends in a state where adding a new play type or a new output format requires no modification of existing code — just addition.
Key takeaway
A first pass through a real example demonstrates that refactoring is a disciplined series of small, test-verified transformations that move code from tangled to modular without ever breaking what works.
Chapter 2 — Principles in Refactoring
Central question
What exactly is refactoring, why should developers do it, when should they do it, and what are its real costs and limits?
Main argument
Defining refactoring precisely
Fowler distinguishes two senses of the word: as a noun, a refactoring is a specific transformation with a name (Extract Function, Move Field); as a verb, refactoring is the activity of applying those transformations in sequence. The defining constraint is that the external behavior of the software does not change. A change that improves structure and adds behavior is not refactoring; it is development. Keeping the two activities distinct is important because mixing them makes debugging harder.
The Two Hats metaphor
Fowler borrows Kent Beck's "two hats" image: at any moment a programmer is either wearing the adding-functionality hat (writing new code, adding tests) or the refactoring hat (restructuring existing code, not adding tests, not changing behavior). You switch hats frequently, but you never wear both at once. The discipline of keeping these modes separate prevents the common trap of "improving" a function and accidentally changing its semantics.
Why refactor? The economic argument
Fowler frames refactoring in economic terms: poorly structured code is slower to read, slower to modify, and more prone to introducing bugs. The long-term cost of not refactoring exceeds the short-term cost of doing it. He cites four specific benefits: (1) it improves the design of software, reversing the entropy that accumulates from changes made without full understanding; (2) it makes software easier to understand, since future readers include the programmer herself six months later; (3) it helps find bugs, because the act of restructuring forces a detailed re-reading of the code; (4) it helps programmers program faster, because good structure supports velocity rather than hindering it.
When to refactor: the Rule of Three
Fowler resists scheduling refactoring as a separate project phase. Instead, it should happen opportunistically: (1) preparatory refactoring — before adding a feature, restructure so the feature fits; (2) comprehension refactoring — when reading code to understand it, restructure it to capture what you've learned; (3) litter-pickup refactoring — fix obvious problems when you encounter them. The Rule of Three (from Don Roberts): do something once, do it again and note the duplication, do it a third time and refactor.
Problems with refactoring
Fowler discusses legitimate obstacles. Slowing down feature delivery in the short term is real, though he argues the long-term trade-off favors refactoring. Branches in source control that diverge for long periods make refactoring painful — the solution is continuous integration with small, frequent merges. Some code is too tangled to refactor safely without a full rewrite; knowing when to rewrite rather than refactor is a judgment call. Databases are especially difficult to refactor because schema changes require migration scripts and coordination across teams.
Refactoring, Architecture, and YAGNI
Traditional software design tries to anticipate future needs and build in flexibility upfront. Refactoring changes the calculus: instead of speculating about what the code will need to do, build the simplest design that satisfies today's requirements, then refactor as understanding evolves. This is the YAGNI principle (You Aren't Gonna Need It). Refactoring does not eliminate upfront design, but it changes design from a one-time gate into a continuous activity.
Refactoring and performance
Refactoring can make code slower in the short term (e.g., replacing a temporary variable with repeated function calls). Fowler's counter-argument is the 90/10 rule: 90% of performance time is spent in 10% of the code. Profiling identifies the real bottlenecks; premature optimization that sacrifices clarity is almost always a bad trade. Refactor first for clarity, then optimize the proven hot spots.
Key ideas
- The formal definition of refactoring excludes behavior changes; violations of this constraint are bugs, not refactorings.
- The Two Hats discipline prevents the most common source of self-inflicted confusion during coding.
- Refactoring is justified economically, not aesthetically; the argument is about the cost of future changes.
- The Rule of Three provides a practical trigger that avoids both under-refactoring and over-engineering.
- YAGNI, enabled by refactoring, is an argument against speculative architecture.
- Performance and clarity are not permanently opposed; profiling separates the code that actually needs speed from the code that merely looks inefficient.
Key takeaway
Refactoring is a disciplined, economically justified practice woven into normal programming, not a remediation activity scheduled after the fact.
Chapter 3 — Bad Smells in Code
Central question
How do you recognize code that needs refactoring before you know which specific refactoring to apply?
Main argument
Written with Kent Beck, this chapter argues that specific structural patterns in code reliably signal design problems. These patterns — called code smells — do not definitively indicate a bug, but they indicate that the design is likely to cause problems as the code evolves. The smells catalog is not algorithmic; judgment is required. Fowler lists 24 named smells.
Naming and comprehension smells
- Mysterious Name: a function, variable, or class whose name does not reveal its purpose. The fix is almost always Rename Function, Rename Variable, or Rename Field.
- Comments: not always a smell, but a comment that explains what a block of code does is often a sign the block should be extracted into a named function.
Duplication smells
- Duplicated Code: the same code structure in more than one place. If two fragments are identical, Extract Function and call it from both. If they are similar but not identical, Slide Statements to bring them together before extracting.
Size and complexity smells
- Long Function: the longer a function, the harder it is to understand. Aggressive extraction of named sub-functions almost always makes code clearer; the concern about function-call overhead is almost never justified in modern runtimes.
- Long Parameter List: long parameter lists are hard to remember and easy to misorder. Introduce Parameter Object or Replace Parameter with Query to shorten them.
- Large Class: a class that has too many responsibilities accumulates too many fields and methods. Extract Class or Extract Superclass to split it.
Coupling and cohesion smells
- Divergent Change: one class is modified for many different reasons. When adding a feature requires touching the same class for two separate concerns, Extract Class to separate those concerns.
- Shotgun Surgery: one logical change requires small edits scattered across many classes. Move Function and Move Field to consolidate them.
- Feature Envy: a method that seems more interested in the data of another class than its own. Move the method toward the data it uses.
- Data Clumps: the same group of data items (e.g., city, zip code, country) appear together in multiple method signatures. Introduce Parameter Object to bundle them.
- Inappropriate Intimacy (called Insider Trading in the 2nd edition): two classes that know too much about each other's internals. Move Function and Move Field to disentangle them.
Data representation smells
- Primitive Obsession: using primitive types (strings, integers) where a richer object would be clearer. Replace Primitive with Object.
- Data Class: a class that holds data but has no behavior. Considered a smell because behavior that properly belongs to the class is scattered elsewhere; the fix is to move the relevant behavior in.
- Mutable Data: mutation is a rich source of bugs because any part of the code can change the value unexpectedly. Split Variable or Replace Derived Variable with Query, Encapsulate Variable.
- Global Data: the most insidious form of mutable data; Encapsulate Variable.
Control flow smells
-
Repeated Switches: a
switchorif-elsechain that checks the same condition in multiple places. Replace Conditional with Polymorphism. - Loops: modern pipelines (map, filter, reduce) are often clearer than explicit loops. Replace Loop with Pipeline.
Hierarchy and relationship smells
- Speculative Generality: code that exists to support future use cases that never materialized. Collapse Hierarchy, Inline Function, or Remove Dead Code.
- Refused Bequest: a subclass that inherits methods it does not use or overrides them to signal they are inapplicable. Replace Subclass with Delegate.
- Middle Man: a class that exists only to delegate to another; Remove Middle Man.
- Lazy Element: a function, class, or module that no longer justifies its existence. Inline it.
- Temporary Field: an object's field is set only in certain circumstances. Extract Class for the contingent behavior.
-
Message Chains:
a.b().c().d(). Hide Delegate to encapsulate the chain.
Key ideas
- Smells are heuristics, not rules; context determines whether a smell is a real problem.
- The smell catalog gives teams a shared vocabulary for code review conversations.
- Many smells relate to a single root cause: code that conflates two distinct responsibilities or two distinct phases.
- Comments as a smell is counterintuitive but important: a comment is often evidence that the code structure is not self-explanatory.
Key takeaway
Named smells give developers a perceptual vocabulary — a way to see structural problems before understanding exactly how to fix them.
Chapter 4 — Building Tests
Central question
What kind of test suite is necessary to make refactoring safe, and how should it be built?
Main argument
Self-testing code
The prerequisite for refactoring is a test suite that runs quickly and fails immediately when behavior changes. Fowler calls this self-testing code. The test suite serves as an error detector: a refactoring step is safe if, after making it, all tests still pass. Without this, a programmer is navigating in the dark — changes that introduce subtle bugs go undetected until much later, when they are far more expensive to diagnose.
The structure of a good test
Fowler demonstrates testing using a simple domain example (a producer/province system). A test sets up a fixture (some objects in a known state), exercises them, and asserts that the result matches an expectation. He uses the describe/it style (Mocha with Chai in the book's examples). Tests should be fast enough to run after every change — the whole suite should complete in seconds.
Testing strategy: probe the boundaries
The most valuable tests are not just the "happy path" cases but the boundary conditions: empty collections, zero values, the first and last items in a range. These are where bugs cluster. Fowler applies the heuristic of testing every condition that could go wrong: What happens when a list is empty? When a quantity is zero? When two names are identical?
How much testing is enough?
Fowler's answer is pragmatic: test every piece of code whose failure would be costly. He explicitly advises against testing getters and setters that do nothing but get and set; those are not the risk. Concentrate testing effort on code that contains branching logic, non-trivial computations, or integration with external data. The quality measure is: "If I introduced a bug here, would a test catch it?"
The connection to TDD
Fowler distinguishes his approach from strict Test-Driven Development. He does not insist on writing tests before code in all circumstances, but he does insist on having tests before refactoring. TDD's "red-green-refactor" cycle uses this same principle: the green phase confirms behavior; the refactor phase is safe because of the green tests.
Key ideas
- Automated tests are not optional for refactoring; they are the mechanism that makes safe transformation possible.
- A fast-running suite that the programmer actually runs constantly is more valuable than a comprehensive suite that takes ten minutes to run.
- Boundary testing finds more bugs than happy-path testing because that is where code logic is most likely to be wrong.
- The goal is not 100% code coverage but confidence: could you detect a defect if one were introduced?
- Tests are also documentation: a well-named test suite describes the intended behavior of a module.
Key takeaway
Refactoring without automated tests is not refactoring — it is guessing; the test suite is the instrumentation that makes each small transformation verifiable.
Chapter 5 — Introducing the Catalog
Central question
How is the refactoring catalog organized, and how should individual refactoring entries be read?
Main argument
The format of each refactoring entry
Each entry in the catalog follows a standard structure: a name, a sketch (a before/after code snippet), a motivation section explaining why you would do this, and a mechanics section giving step-by-step instructions. The mechanics are intentionally detailed and conservative — they describe how to make the transformation in the smallest possible safe steps, including when to run the tests. For experienced practitioners who already understand the pattern, the mechanics can be skimmed; they are primarily for learning the safe rhythm of small steps.
The choice of refactorings
The catalog does not attempt to be exhaustive. Fowler selects refactorings that are (a) commonly needed, (b) non-obvious, and (c) amenable to a safe, step-by-step procedure. Many valid code improvements are not in the catalog simply because they are too context-specific or too obvious to warrant a named entry. The catalog is a reference, not an algorithm.
Naming matters
A refactoring's name is its most important property, because names are what enable teams to talk about restructuring in shorthand. "Extract Function" is a conversation-stopper in the best sense: both parties know immediately what is being proposed. Fowler changed several names from the 1st edition (Extract Method → Extract Function, Inline Method → Inline Function) to reflect a less class-centric view of the world.
Key ideas
- The catalog format (motivation + mechanics) separates the why from the how, allowing the reader to adopt whichever part is currently unfamiliar.
- The step-by-step mechanics exist to build the safe-step habit; experienced refactorers internalize them and work faster, but the habit comes first.
- The naming convention for refactorings is part of the book's contribution to professional vocabulary.
Key takeaway
The catalog is a reference library of named, structured transformations — its value lies as much in the shared vocabulary it creates as in the technical instructions it provides.
Chapter 6 — A First Set of Refactorings
Central question
What are the most fundamental and frequently needed refactorings, and how are they applied safely?
Main argument
This chapter covers the 11 refactorings Fowler considers the most essential — the ones a programmer should have at their fingertips before any others.
Extract Function and Inline Function
Extract Function is the single most commonly applied refactoring: take a fragment of code and move it into a new function whose name explains what that fragment does. The guiding principle is the separation of intention (what the code does) from implementation (how it does it). When a fragment's intention is clear from its context, extract it and name it. The mechanics require identifying all local variables used in the fragment; any variable that is modified inside the fragment must become a return value.
The inverse, Inline Function, applies when a function's body is as clear as its name, or when a poorly factored group of functions needs to be collapsed and re-extracted differently. Both refactorings together give the programmer control over the granularity of decomposition.
Extract Variable and Inline Variable
Extract Variable assigns a complex expression to a named temporary variable, making the expression's purpose explicit. It is the intra-function counterpart to Extract Function. Inline Variable removes a variable whose name adds no clarity over the expression itself. Together they let a programmer tune the level of commentary embedded in code.
Change Function Declaration
This covers renaming functions and changing their parameter lists. Names are the programmer's primary tool for communicating intent; renaming a function that has been misnamed is always worth doing. The mechanics include a migration strategy for changing a function's signature without breaking all callers at once: add the new signature as a wrapper, migrate callers, then remove the old version.
Encapsulate Variable
For data that is accessed widely, wrapping it in getter/setter functions provides a chokepoint for future change. Any code that reads or writes the variable can be intercepted. This is especially important for mutable data shared across modules.
Rename Variable
Variable names carry meaning. A variable named a is opaque; one named customerAccount is self-documenting. The scope of a variable determines how urgently renaming matters: a loop counter that exists for two lines needs no ceremony; a field that exists for the life of an object deserves a precise name.
Introduce Parameter Object
When a group of data items always travel together across function calls — start date and end date, city and postal code — bundle them into a single object. This reveals that the group has its own identity and creates a home for any behavior that applies to the group as a whole.
Combine Functions into Class and Combine Functions into Transform
When a cluster of functions all operate on the same data, Combine Functions into Class gathers them and the data they share into a class. Combine Functions into Transform is an alternative for contexts where immutability is preferred: a single transform function takes the input data and returns an enriched copy with all derived values pre-computed.
Split Phase
When a section of code does two distinct things in sequence — first computing data, then using it — Split Phase separates them into two functions with a clear data-structure handoff between them. The benefit is that each phase can be understood and modified independently.
Key ideas
- Extract Function is the most important single refactoring; virtually everything else depends on having well-named, well-scoped functions.
- The mechanics of Extract Function are precise because the most common error — failing to handle modified local variables — can silently change behavior.
- Extract Function vs. Inline Function illustrates that there is no canonical level of decomposition; the right level depends on what makes the code clearest at this moment.
- Introduce Parameter Object is a gateway to richer domain modeling: once a group of values has a name, it attracts behavior.
- Split Phase enforces a clean separation of concerns at the intra-function level.
Key takeaway
These eleven refactorings are the daily toolkit; fluency with them transforms the mechanical act of coding into a continuous act of design.
Chapter 7 — Encapsulation
Central question
How can data and internal complexity be hidden so that code is more modular and less fragile under change?
Main argument
Encapsulation is the most important design tool for managing how modules interact. Code that directly reads and writes another module's internal data is tightly coupled to it; any change to the data structure ripples through all accessing code. The refactorings in this chapter establish explicit boundaries.
Encapsulate Record
A plain data record (a JavaScript object literal, a struct) exposes all its fields directly. Encapsulate Record wraps it in a class with getter/setter methods, making it possible to see all accesses and to control what clients can read or write. The mechanics involve creating a class, moving the record into it, and gradually replacing direct field accesses with method calls.
Encapsulate Collection
Collections (arrays, sets, maps) are especially dangerous to expose because any caller can modify the collection in place. Encapsulate Collection ensures that callers who want to add or remove items must do so through the owning class's methods, which can enforce invariants or notify interested parties.
Replace Primitive with Object
A single value (a telephone number stored as a string, a priority stored as an integer) often accumulates behavior over time: formatting logic, comparison logic, validation. Replace Primitive with Object creates a proper class for the value, centralizing that behavior. The class starts small but provides a home for everything that properly belongs to that concept.
Replace Temp with Query
A local variable that holds the result of an expression can be replaced by a method call. The benefit is that the method can be called from anywhere in the class, reducing code duplication; the cost is a potential performance hit (usually negligible for business logic).
Extract Class and Inline Class
Extract Class is the antidote to the Large Class smell: identify a cohesive subset of fields and methods and move them to a new class. Inline Class is the inverse: when a class no longer has enough responsibility to justify its existence, merge it into another class.
Hide Delegate and Remove Middle Man
Hide Delegate adds a method to a server class that forwards calls to a delegate, so that clients do not need to know the delegate exists (person.manager() instead of person.department().manager()). Remove Middle Man is the inverse: if the server class has accumulated so many delegation methods that it has become a thin pass-through, remove the intermediary and let clients call the delegate directly. The right balance depends on how stable the delegate's interface is.
Substitute Algorithm
Replace the body of a function with a cleaner implementation that produces the same result. This is the purest form of the idea that implementation and interface are separate concerns.
Key ideas
- Encapsulation is not about hiding data for its own sake; it is about controlling the surface area through which change propagates.
- Every exposed field in a data record is a potential coupling point; encapsulating the record makes future change cheaper.
- Replace Primitive with Object is often the first step toward a richer domain model; once the object exists, behavior migrates naturally to it.
- Hide Delegate vs. Remove Middle Man is a pragmatic judgment about interface stability; neither is universally correct.
Key takeaway
Encapsulation converts implicit, widely-distributed couplings into explicit, localized contracts, making each module's internals independently changeable.
Chapter 8 — Moving Features
Central question
How do you move code to the place where it belongs when the initial structure turns out to be wrong?
Main argument
One of the most common structural problems is code that lives in the wrong place: a function that knows too much about another class, a field that is more relevant to a different module, a loop that does two separate things. The refactorings in this chapter move elements of code to improve cohesion and reduce coupling.
Move Function
The most fundamental placement refactoring: move a function from one class or module to another when it makes more sense there. The trigger is Feature Envy: a function that references more data from another context than from its own. The mechanics involve creating the new function in its destination, directing the old location to delegate to it, then removing the old version once all callers are updated.
Move Field
Move a data field to a different class when it is more frequently referenced there. Often accompanies Move Function, since data and the behavior that uses it belong together.
Move Statements into Function and Move Statements to Callers
Move Statements into Function takes code that is always executed together with a function call and pulls it into the function body — eliminating the repetition at every call site. Move Statements to Callers is the inverse: when a function does something that only some callers need, push that behavior out to the callers and leave the function with its common core.
Replace Inline Code with Function Call
When inline code duplicates the behavior of an existing function, replace it with a call to that function.
Slide Statements
Code that belongs together is easier to understand when it is physically adjacent. This simple refactoring moves statements so that related declarations and their uses are closer, often as a precursor to Extract Function.
Split Loop
A loop that does two things at once (accumulating a sum and a string, for instance) can be split into two separate loops. The performance cost is usually negligible (a second O(n) pass over an already-traversed collection), and the clarity gain is significant.
Replace Loop with Pipeline
Collections operations expressed as map, filter, and reduce are often easier to read than equivalent for loops because each stage declares its intent as a named operation. Replace Loop with Pipeline transforms imperative loops into functional pipelines.
Remove Dead Code
Code that is never executed is not harmless; it confuses readers into wondering why it exists and whether it was intentionally disabled. Remove it without ceremony. Version control remembers it if it is ever needed again.
Key ideas
- Code placement is a design decision, not just an organizational preference; moving code toward the data it uses reduces coupling.
- Feature Envy is the diagnostic: a function that spends most of its time looking at another class's data belongs in that class.
- Split Loop is counterintuitive — splitting a loop seems less efficient — but the performance argument almost never holds up under profiling.
- Replace Loop with Pipeline rewards the reader with immediate comprehension of intent at the cost of learning the pipeline idiom.
Key takeaway
Placing each function and field in the module that owns the relevant data is the foundation of good cohesion; these refactorings are the tools for correcting initial misplacement.
Chapter 9 — Organizing Data
Central question
How should data structures be reshaped to make code that uses them clearer and more robust?
Main argument
Data structures — variables, fields, records, references — carry enormous implicit assumptions about how values are used and whether they can change. This chapter covers refactorings that make those assumptions explicit and correct the most common data-organization errors.
Split Variable
A variable that is assigned to more than once for different purposes is a source of confusion. A loop counter that is reused as a running total is the classic example. Split Variable gives each purpose its own variable with its own name. The exception is collecting variables (accumulators like sum or concatenation) that are intentionally updated on each iteration.
Rename Field
Field names in data structures are the most permanent labels in a codebase; getting them right matters as much as naming functions. Renaming a widely-used field is a significant operation, often requiring access encapsulation as a first step so that the rename can be applied mechanically.
Replace Derived Variable with Query
A variable that holds a value computed from other data is a liability: it must be kept in sync with the data it was derived from, and every mutation to the underlying data must remember to update it. Replacing it with a function that recomputes on demand eliminates the sync problem entirely.
Change Reference to Value and Change Value to Reference
An object used as a value (immutable; equality defined by content) is simpler to reason about than one used as a reference (mutable; equality defined by identity). Change Reference to Value converts a mutable reference object to an immutable value by removing setting methods and replacing field assignment with object replacement. The inverse, Change Value to Reference, is needed when multiple objects need to share a single mutable entity and see updates to it.
Key ideas
- Variables that serve multiple purposes simultaneously obscure the intent of each; Split Variable is almost always worth doing when the purposes are genuinely different.
- The choice between reference semantics and value semantics has large downstream consequences for concurrency, testing, and code clarity.
- Replace Derived Variable with Query is a specific application of the principle that mutable state should be minimized; derived data does not need to be stored.
- Field renaming, while seemingly trivial, often triggers the realization that the original concept was misunderstood.
Key takeaway
Data structures encode assumptions; reshaping them to match the actual domain concepts removes an entire class of synchronization bugs and clarifies the mental model.
Chapter 10 — Simplifying Conditional Logic
Central question
How can complex conditional logic — the most common source of accidental complexity in code — be made simpler?
Main argument
Conditionals are necessary, but long chains of if-else, nested ternaries, and repeated switch statements bury intent under mechanism. The refactorings here attack conditional complexity from multiple angles.
Decompose Conditional
A complex condition or a long branch body is extracted into named functions: if (isNotSummer(date)) is more legible than the raw date comparison it represents. This applies the Extract Function principle to conditions and branches specifically, using naming to convey intent.
Consolidate Conditional Expression
A sequence of conditions that all result in the same outcome can be merged into a single condition. The merged expression can then be extracted into a function whose name explains what the combined condition means.
Replace Nested Conditional with Guard Clauses
Functions that have a normal execution path buried under multiple layers of precondition checks benefit from guard clauses: early returns that handle the exceptional cases upfront, leaving the normal flow unindented and prominent. The linear if-else structure (if A { if B { if C { ... } } }) is replaced by a series of early exits (if !A return; if !B return; if !C return; ...), making the main flow the default rather than the innermost branch.
Replace Conditional with Polymorphism
When a conditional switches on the type of an object to choose behavior, the type-specific behavior belongs in the type itself. Replacing each branch of the switch with an overriding method in the appropriate subclass (or a different class implementing a common interface) moves the logic to where it conceptually belongs and eliminates the switch entirely. New types are added by creating a new class, not by editing an existing switch.
Introduce Special Case
Many conditionals check for a specific value (often null or some sentinel) and handle it separately. When the same null check appears repeatedly, Introduce Special Case creates a special-case object that returns appropriate default values for all queries, so that callers no longer need to check.
Introduce Assertion
An assertion states explicitly that a condition is assumed to be true at a particular point in the code. Unlike a test, it documents an assumption that the programmer expects will never be violated in production. Assertions make implicit assumptions explicit and serve as internal documentation.
Key ideas
- Guard clauses are more readable than deeply nested conditions because the reader can parse exceptional cases immediately, then focus entirely on the main flow.
- Replace Conditional with Polymorphism is appropriate when the same type-based switch appears multiple times; a single occurrence may not justify the overhead.
- Introduce Special Case is a specific application of Replace Conditional with Polymorphism for null/default values; it eliminates null-checking noise throughout the codebase.
- Introduce Assertion documents a contract without the overhead of a runtime test that the code already implicitly depends on.
Key takeaway
Conditional complexity is not inevitable; systematic extraction, early-exit structuring, and polymorphic dispatch each address a different pattern of tangled branching.
Chapter 11 — Refactoring APIs
Central question
How should function and method interfaces be shaped to be the clearest, most flexible contracts between modules?
Main argument
A function's signature is a contract with all its callers. A poorly shaped API — one that mixes query and command, accepts a flag argument that changes fundamental behavior, or requires callers to reassemble data they already have — accumulates coupling and confusion over time. This chapter covers refactorings that improve how functions present themselves to the rest of the codebase.
Separate Query from Modifier
A function that both returns a value and changes state combines two concerns that should be separate. Separate Query from Modifier splits it into a query (returns a value, no side effects) and a command (has side effects, returns nothing). This follows the Command-Query Separation principle: queries can be called freely anywhere; commands must be called with care.
Parameterize Function
When two functions do the same thing but differ by a constant value (e.g., a 10% raise vs. a 5% raise), they can be unified into a single function that takes the value as a parameter. This reduces duplication and makes the relationship between the two behaviors explicit.
Remove Flag Argument
A boolean (or enum) parameter that selects an entirely different behavior inside the function is a flag argument. It forces callers to pass cryptic true/false values and hides the fact that the function is really two different functions. Replace it with two explicitly named functions.
Preserve Whole Object
When a function receives several fields extracted from the same object, pass the whole object instead. This reduces the parameter list length, makes the function's dependency explicit, and means that adding a new field to the computation does not require changing the function's signature.
Replace Parameter with Query and Replace Query with Parameter
Replace Parameter with Query removes a parameter that the function could compute itself from accessible data. Replace Query with Parameter does the reverse: it makes a function independent of some global or contextual state by passing that state in explicitly, improving testability and reducing hidden dependencies.
Remove Setting Method
If a field should only be set at construction time, removing its setter makes that constraint explicit and creates an immutable object.
Replace Constructor with Factory Function
Constructors are constrained: they must return the type they are defined on, they cannot have descriptive names beyond the class name. Replace Constructor with Factory Function wraps the constructor in a named function that can return any subtype, can have a meaningful name (createEngineer() vs. new Employee("engineer")), and can apply creation logic.
Replace Function with Command and Replace Command with Function
A command object wraps a function call in an object with a single execute method. This allows the function's operation to be stored, queued, undone, and enriched with additional operations. When a function does not need any of these capabilities, Replace Command with Function simplifies it back to a plain function.
Key ideas
- Command-Query Separation makes code easier to reason about because functions that return values are guaranteed not to change state.
- Flag arguments are a code smell because they obscure the callee's intent at the call site;
setDimension(true)communicates nothing. - Factory functions decouple instantiation from class hierarchy, making the creation logic more flexible.
- Command objects are powerful but heavyweight; they are appropriate when the overhead of the class structure pays for itself through undo, logging, or queuing.
Key takeaway
A well-shaped API communicates intent clearly at every call site and minimizes the coupling between the caller's world and the function's implementation.
Chapter 12 — Dealing with Inheritance
Central question
How can inheritance hierarchies be restructured to avoid fragility, over-coupling, and inappropriate coupling between parent and child classes?
Main argument
Inheritance is the most powerful — and the most abused — mechanism in object-oriented design. It is tempting to use inheritance whenever two classes share any behavior, but the result is often fragile coupling: changes to a superclass unexpectedly break subclasses. This chapter covers both how to fix broken hierarchies and how to decide when inheritance is the wrong tool entirely.
Moving features up and down the hierarchy
Pull Up Method and Pull Up Field move shared behavior and data to the superclass to eliminate duplication. Push Down Method and Push Down Field move behavior that is specific to one subclass down to that subclass, keeping the superclass clean. Pull Up Constructor Body consolidates common constructor logic in the superclass.
Replace Type Code with Subclasses
When a class uses a type field (an integer or string that represents a kind of thing) to vary its behavior through conditionals, Replace Type Code with Subclasses creates a subclass for each type. The subclasses override the relevant methods, eliminating the conditionals from the superclass. This is the prerequisite for Replace Conditional with Polymorphism at the class level.
Remove Subclass
When a subclass is no longer different enough from its parent to justify its existence — perhaps the type code that drove its creation is rarely populated — Remove Subclass merges it back into the parent.
Extract Superclass
When two classes have overlapping behavior, they may benefit from a common superclass. Extract Superclass creates that superclass and moves the shared behavior into it. This is a bottom-up form of generalization, as opposed to top-down design.
Collapse Hierarchy
When a superclass and a subclass have grown so similar that the subclass adds nothing, merge them.
Replace Subclass with Delegate and Replace Superclass with Delegate
The most architecturally significant refactorings in this chapter. Replace Subclass with Delegate converts a subclass into a component object held by the parent. This eliminates the coupling that comes from sharing an implementation hierarchy: the parent class delegates to the component for the behavior that varied, and the component can be swapped or extended without the parent knowing. Replace Superclass with Delegate does the same for parent-class relationships: instead of inheriting from a class, hold an instance of it and forward the relevant calls.
These two refactorings embody the principle "favor composition over inheritance". Inheritance is appropriate when there is a genuine is-a relationship and the subclass is best understood as a specialization of the parent. When the relationship is more "uses behavior from" than "is a kind of," delegation is cleaner.
Key ideas
- Pulling up shared behavior reduces duplication; pushing down non-shared behavior reduces noise in the superclass interface.
- Replace Type Code with Subclasses is the bridge between type-dispatching code and a proper polymorphic design.
- The limit of inheritance is the constraint that an object can have only one parent; delegation is not constrained this way.
- Replace Subclass/Superclass with Delegate is often the right move when behavior variations cross-cut the type hierarchy — when an object's behavior varies along two independent dimensions.
- Collapse Hierarchy is the admission that a design speculated about a variation that never materialized.
Key takeaway
Inheritance is a powerful but narrow tool — appropriate only when the is-a relationship is genuine — and delegation is the more flexible alternative for most other cases where code reuse is the goal.
The book's overall argument
Chapter 1 (Refactoring: A First Example) — Demonstrates through a complete worked example that refactoring is a series of small, named, test-backed transformations that progressively improve structure without breaking behavior, making the abstract principles concrete before stating them.
Chapter 2 (Principles in Refactoring) — Defines refactoring precisely, frames it as an economically justified continuous activity rather than a remediation project, and establishes the Two Hats discipline that keeps adding behavior and improving structure as separate, non-conflated modes.
Chapter 3 (Bad Smells in Code) — Provides the diagnostic vocabulary — 24 named code smells — that lets a programmer recognize structural problems before knowing exactly how to fix them, giving names to what experienced developers sense intuitively.
Chapter 4 (Building Tests) — Establishes the precondition: refactoring is only safe when a comprehensive, fast-running automated test suite exists to detect any accidental behavior change, and shows how to build one.
Chapter 5 (Introducing the Catalog) — Explains the format of the remaining chapters (motivation + mechanics) and why named refactorings with standardized structures create shared vocabulary for teams.
Chapter 6 (A First Set of Refactorings) — Presents the 11 most fundamental refactorings — centered on extracting and naming code fragments — which form the core daily toolkit for any programmer.
Chapter 7 (Encapsulation) — Extends the toolkit to the module-boundary level: hiding data and internal complexity behind explicit interfaces to reduce coupling and make change cheaper.
Chapter 8 (Moving Features) — Addresses structural misplacement: moving functions and data to the context where they belong, following cohesion principles to reduce Feature Envy and Shotgun Surgery.
Chapter 9 (Organizing Data) — Reshapes data structures themselves — splitting variables, converting references to values, replacing derived storage with queries — to eliminate synchronization bugs and clarify semantics.
Chapter 10 (Simplifying Conditional Logic) — Attacks the most common source of accidental complexity: complex conditionals, replaced via extraction, guard clauses, polymorphism, and special-case objects.
Chapter 11 (Refactoring APIs) — Polishes the interfaces between modules: separating commands from queries, eliminating flag arguments, creating factory functions, and deciding when command objects are warranted.
Chapter 12 (Dealing with Inheritance) — Addresses the most structurally significant coupling in object-oriented code: fixing broken hierarchies and, when inheritance is the wrong tool, replacing it with delegation.
Common misunderstandings
Misunderstanding: Refactoring means rewriting large sections of code
The book defines refactoring as a series of small, behavior-preserving transformations. A single refactoring session might change ten lines. Large rewrites that change behavior are not refactorings; they are redesigns. The key property is that each step leaves the tests green.
Misunderstanding: Refactoring should be scheduled as a separate "cleanup sprint"
Fowler explicitly argues against this. Refactoring is woven into feature development: you refactor before adding a feature (preparatory), while reading code to understand it (comprehension), and when you spot a problem in passing (litter-pickup). A cleanup sprint is a sign that refactoring has been neglected too long, not a model to emulate.
Misunderstanding: Good code never needs refactoring
Even code written by skilled developers needs refactoring because requirements change. Code that was correctly structured for last year's requirements may be awkward for this year's. Refactoring is not remediation of bad code; it is the ongoing adaptation of code to changing understanding.
Misunderstanding: Refactoring is only for object-oriented code
The 2nd edition deliberately moves away from Java and class-centric examples to JavaScript, including functional-style patterns (Replace Loop with Pipeline, Combine Functions into Transform). Many of the refactorings apply equally to functional, procedural, and object-oriented contexts.
Misunderstanding: Performance and clean code are in conflict
Fowler addresses this directly: refactoring may introduce small inefficiencies (additional function calls, recomputed values) but the benefit is that well-structured code is easier to profile and easier to optimize. Premature optimization of unstructured code is harder and riskier than optimizing already-clean code at the proven bottleneck.
Misunderstanding: You can refactor without tests
Some developers refactor by "being careful." Fowler's position is that this is not refactoring — it is editing without a safety net. Tests are not a stylistic preference; they are the mechanism that makes safe transformation possible. Without them, even a rename can introduce a subtle bug.
Central paradox / key insight
The central paradox of refactoring is that slowing down to improve code structure makes you faster.
From a naive economic view, refactoring costs time without adding features. The counterargument — which Fowler makes explicit and which the entire book demonstrates — is that code structure is the dominant variable in developer productivity over any timescale longer than a few days. The longer a codebase lives, the more each additional feature is built on top of the accumulated structure of prior features. Cruft in that structure multiplies the cost of every subsequent change.
The true cost of software is not in the initial writing but in all the changes made to it afterward.
This leads to Fowler's most cited line of advice: when you need to add a feature and the code makes it hard, refactor first to make it easy, then add the feature. Spending an hour on structural improvement before a feature takes the hour and repays it many times over in the reduced cost of the feature and every feature that follows.
The corollary is equally important: the discipline of small steps — make one change, run the tests, commit — is what makes refactoring safe. Large-scale restructuring without this discipline is risky not because the idea is wrong but because the execution lacks the error-detection loop. The "two steps forward" of refactoring depends entirely on the "one step back" mechanism of the failing test.
Important concepts
Refactoring (noun)
A named, behavior-preserving code transformation with a standard motivation and step-by-step mechanics. Examples: Extract Function, Move Field, Replace Conditional with Polymorphism.
Refactoring (verb)
The activity of applying a sequence of refactorings to improve code structure without changing observable behavior.
Code smell
A surface indication in source code that something may be wrong with the design. Not a definitive diagnosis, but a heuristic that experienced developers use to decide where to look. Examples: Long Function, Feature Envy, Repeated Switches.
The Two Hats
Kent Beck's metaphor for the discipline of keeping two programming modes separate: the adding-functionality hat (write new code, add tests) and the refactoring hat (restructure existing code, change no behavior). You switch frequently but never wear both simultaneously.
Self-testing code
A codebase equipped with a comprehensive automated test suite that runs quickly enough to be executed after every small change. The prerequisite for safe refactoring.
Preparatory refactoring
Refactoring done immediately before adding a feature, to shape the code so the feature fits cleanly. The trigger is: "This would be easy to add if the code were structured differently."
YAGNI (You Aren't Gonna Need It)
The principle that you should not build flexibility into code to support speculative future requirements. Combined with refactoring, it argues for building the simplest current design and refactoring when actual new requirements arrive.
Command-Query Separation (CQS)
The principle that functions should either return a value (query) or change state (command), but not both. Violated by functions that return values with side effects. Enforced by Separate Query from Modifier.
Feature Envy
A code smell where a function or method references more data from another module than from its own. Diagnostic signal that the function probably belongs in the other module.
Guard clause
An early return statement at the top of a function that handles a special or exceptional case before the main logic begins. A tool for inverting nested conditionals into linear early-exit structures.
Delegation (vs. inheritance)
An object relationship where one object holds a reference to another and forwards calls to it, rather than inheriting behavior from a parent class. More flexible than inheritance because any number of delegates can be composed, and delegates can be swapped at runtime.
Technical debt
Accumulated cruft in a codebase that makes future change slower and more expensive. Refactoring is the primary mechanism for paying down technical debt incrementally. The metaphor implies that carrying the debt has an ongoing interest cost.
Extract Function
The most fundamental refactoring: move a code fragment into a new function named after its purpose. The function name explains the intention; the body contains the implementation. The separation of intention from implementation is the key design principle.
References and Web Links
Primary book and edition information
- Fowler, Martin. Refactoring: Improving the Design of Existing Code, 2nd ed. Addison-Wesley Professional, 2018. ISBN 9780134757599.
Author's own resources
- Martin Fowler's book page for Refactoring
- Fowler's article on what changed in the 2nd edition
- Detailed changes between 1st and 2nd edition catalog
- Online refactoring catalog (companion to the book)
Background and overview
Key concepts referenced in the book
- Martin Fowler's bliki entry on Self-Testing Code
- Martin Fowler's bliki entry on YAGNI
- Martin Fowler's software testing guide
Additional chapter summaries and study resources
These are secondary summaries and should be used alongside, rather than instead of, the original book.