Refactoring Catalog
A curated catalog of refactoring techniques from Martin Fowler’s Refactoring (2nd Edition). Each refactoring includes motivation, step-by-step mechanics, and examples.
“A refactoring is defined by its mechanics—the precise sequence of steps that you follow to carry out the change.” — Martin Fowler
How to Use This Catalog
- Identify the smell using the code smells reference
- Find the matching refactoring in this catalog
- Follow the mechanics step by step
- Test after each step to ensure behavior is preserved
Golden Rule: If any step takes more than 10 minutes, break it into smaller steps.
Most Common Refactorings
Extract Method
When to use: Long method, duplicate code, need to name a concept
Motivation: Turn a code fragment into a method whose name explains the purpose.
Mechanics:
- Create a new method named for what it does (not how)
- Copy the code fragment into the new method
- Scan for local variables used in the fragment
- Pass local variables as parameters (or declare in method)
- Handle return values appropriately
- Replace the original fragment with a call to the new method
- Test
Before:
function printOwing(invoice) { let outstanding = 0;
console.log("***********************"); console.log("**** Customer Owes ****"); console.log("***********************");
// Calculate outstanding for (const order of invoice.orders) { outstanding += order.amount; }
// Print details console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`);}After:
function printOwing(invoice) { printBanner(); const outstanding = calculateOutstanding(invoice); printDetails(invoice, outstanding);}
function printBanner() { console.log("***********************"); console.log("**** Customer Owes ****"); console.log("***********************");}
function calculateOutstanding(invoice) { return invoice.orders.reduce((sum, order) => sum + order.amount, 0);}
function printDetails(invoice, outstanding) { console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`);}Inline Method
When to use: Method body is as clear as its name, excessive delegation
Motivation: Remove needless indirection when the method doesn’t add value.
Mechanics:
- Check that the method isn’t polymorphic
- Find all calls to the method
- Replace each call with the method body
- Test after each replacement
- Remove the method definition
Before:
function getRating(driver) { return moreThanFiveLateDeliveries(driver) ? 2 : 1;}
function moreThanFiveLateDeliveries(driver) { return driver.numberOfLateDeliveries > 5;}After:
function getRating(driver) { return driver.numberOfLateDeliveries > 5 ? 2 : 1;}Extract Variable
When to use: Complex expression that is hard to understand
Motivation: Give a name to a piece of a complex expression.
Mechanics:
- Ensure the expression has no side effects
- Declare an immutable variable
- Set it to the result of the expression (or part)
- Replace the original expression with the variable
- Test
Before:
return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1, 100);After:
const basePrice = order.quantity * order.itemPrice;const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;const shipping = Math.min(basePrice * 0.1, 100);return basePrice - quantityDiscount + shipping;Inline Variable
When to use: Variable name doesn’t communicate more than the expression
Motivation: Remove unnecessary indirection.
Mechanics:
- Check that the right-hand side has no side effects
- If variable isn’t immutable, make it so and test
- Find the first reference and replace with the expression
- Test
- Repeat for all references
- Remove the declaration and assignment
- Test
Rename Variable
When to use: Name doesn’t clearly communicate purpose
Motivation: Good names are crucial for clean code.
Mechanics:
- If variable is widely used, consider encapsulating
- Find all references
- Change each reference
- Test
Tips:
- Use intention-revealing names
- Avoid abbreviations
- Use domain terminology
// Badconst d = 30;const x = users.filter(u => u.a);
// Goodconst daysSinceLastLogin = 30;const activeUsers = users.filter(user => user.isActive);Change Function Declaration
When to use: Function name doesn’t explain purpose, parameters need change
Motivation: Good function names make code self-documenting.
Mechanics (Simple):
- Remove parameters not needed
- Change the name
- Add parameters needed
- Test
Mechanics (Migration - for complex changes):
- If removing parameter, make sure it’s not used
- Create new function with desired declaration
- Have old function call new function
- Test
- Change callers to use new function
- Test after each
- Remove old function
Before:
function circum(radius) { return 2 * Math.PI * radius;}After:
function circumference(radius) { return 2 * Math.PI * radius;}Encapsulate Variable
When to use: Direct access to data from multiple places
Motivation: Provide a clear access point for data manipulation.
Mechanics:
- Create getter and setter functions
- Find all references
- Replace reads with getter
- Replace writes with setter
- Test after each change
- Restrict visibility of the variable
Before:
let defaultOwner = { firstName: "Martin", lastName: "Fowler" };
// Used in many placesspaceship.owner = defaultOwner;After:
let defaultOwnerData = { firstName: "Martin", lastName: "Fowler" };
function defaultOwner() { return defaultOwnerData; }function setDefaultOwner(arg) { defaultOwnerData = arg; }
spaceship.owner = defaultOwner();Introduce Parameter Object
When to use: Several parameters that frequently go together
Motivation: Group data that naturally belongs together.
Mechanics:
- Create a new class/structure for the grouped parameters
- Test
- Use Change Function Declaration to add the new object
- Test
- For each parameter in the group, remove it from the function and use the new object
- Test after each
Before:
function amountInvoiced(startDate, endDate) { ... }function amountReceived(startDate, endDate) { ... }function amountOverdue(startDate, endDate) { ... }After:
class DateRange { constructor(start, end) { this.start = start; this.end = end; }}
function amountInvoiced(dateRange) { ... }function amountReceived(dateRange) { ... }function amountOverdue(dateRange) { ... }Combine Functions into Class
When to use: Several functions operate on the same data
Motivation: Group functions with the data they operate on.
Mechanics:
- Apply Encapsulate Record to the common data
- Move each function into the class
- Test after each move
- Replace data arguments with uses of class fields
Before:
function base(reading) { ... }function taxableCharge(reading) { ... }function calculateBaseCharge(reading) { ... }After:
class Reading { constructor(data) { this._data = data; }
get base() { ... } get taxableCharge() { ... } get calculateBaseCharge() { ... }}Split Phase
When to use: Code deals with two different things
Motivation: Separate code into distinct phases with clear boundaries.
Mechanics:
- Create a second function for the second phase
- Test
- Introduce an intermediate data structure between phases
- Test
- Extract first phase into its own function
- Test
Before:
function priceOrder(product, quantity, shippingMethod) { const basePrice = product.basePrice * quantity; const discount = Math.max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate; const shippingPerCase = (basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountedFee : shippingMethod.feePerCase; const shippingCost = quantity * shippingPerCase; return basePrice - discount + shippingCost;}After:
function priceOrder(product, quantity, shippingMethod) { const priceData = calculatePricingData(product, quantity); return applyShipping(priceData, shippingMethod);}
function calculatePricingData(product, quantity) { const basePrice = product.basePrice * quantity; const discount = Math.max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate; return { basePrice, quantity, discount };}
function applyShipping(priceData, shippingMethod) { const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountedFee : shippingMethod.feePerCase; const shippingCost = priceData.quantity * shippingPerCase; return priceData.basePrice - priceData.discount + shippingCost;}Moving Features
Move Method
When to use: Method uses more features of another class than its own
Motivation: Put functions with the data they use most.
Mechanics:
- Examine all program elements used by method in its class
- Check if method is polymorphic
- Copy method to target class
- Adjust for new context
- Make original method delegate to target
- Test
- Consider removing original method
Move Field
When to use: Field is used more by another class
Motivation: Keep data with the functions that use it.
Mechanics:
- Encapsulate the field if not already
- Test
- Create field in target
- Update references to use target field
- Test
- Remove original field
Move Statements into Function
When to use: Same code always appears with a function call
Motivation: Remove duplication by moving repeated code into the function.
Mechanics:
- Extract the repeated code into a function if not already
- Move statements into that function
- Test
- If callers no longer need standalone statements, remove them
Move Statements to Callers
When to use: Common behavior varies between callers
Motivation: When behavior needs to differ, move it out of the function.
Mechanics:
- Use Extract Method on the code to move
- Use Inline Method on the original function
- Remove the now-inlined call
- Move extracted code to each caller
- Test
Organizing Data
Replace Primitive with Object
When to use: Data item needs more behavior than simple value
Motivation: Encapsulate data with its behavior.
Mechanics:
- Apply Encapsulate Variable
- Create a simple value class
- Change the setter to create a new instance
- Change the getter to return the value
- Test
- Add richer behavior to the new class
Before:
class Order { constructor(data) { this.priority = data.priority; // string: "high", "rush", etc. }}
// Usageif (order.priority === "high" || order.priority === "rush") { ... }After:
class Priority { constructor(value) { if (!Priority.legalValues().includes(value)) throw new Error(`Invalid priority: ${value}`); this._value = value; }
static legalValues() { return ['low', 'normal', 'high', 'rush']; } get value() { return this._value; }
higherThan(other) { return Priority.legalValues().indexOf(this._value) > Priority.legalValues().indexOf(other._value); }}
// Usageif (order.priority.higherThan(new Priority("normal"))) { ... }Replace Temp with Query
When to use: Temporary variable holds result of an expression
Motivation: Make the code clearer by extracting the expression into a function.
Mechanics:
- Check that the variable is assigned only once
- Extract the assignment’s right-hand side into a method
- Replace references to the temp with the method call
- Test
- Remove the temp declaration and assignment
Before:
const basePrice = this._quantity * this._itemPrice;if (basePrice > 1000) { return basePrice * 0.95;} else { return basePrice * 0.98;}After:
get basePrice() { return this._quantity * this._itemPrice;}
// In the methodif (this.basePrice > 1000) { return this.basePrice * 0.95;} else { return this.basePrice * 0.98;}Simplifying Conditional Logic
Decompose Conditional
When to use: Complex conditional (if-then-else) statement
Motivation: Make the intention clear by extracting conditions and actions.
Mechanics:
- Apply Extract Method on the condition
- Apply Extract Method on the then-branch
- Apply Extract Method on the else-branch (if present)
Before:
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) { charge = quantity * plan.summerRate;} else { charge = quantity * plan.regularRate + plan.regularServiceCharge;}After:
if (isSummer(aDate, plan)) { charge = summerCharge(quantity, plan);} else { charge = regularCharge(quantity, plan);}
function isSummer(date, plan) { return !date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd);}
function summerCharge(quantity, plan) { return quantity * plan.summerRate;}
function regularCharge(quantity, plan) { return quantity * plan.regularRate + plan.regularServiceCharge;}Consolidate Conditional Expression
When to use: Multiple conditions with the same result
Motivation: Make it clear that conditions are a single check.
Mechanics:
- Verify no side effects in conditions
- Combine conditions using
andoror - Consider Extract Method on the combined condition
Before:
if (employee.seniority < 2) return 0;if (employee.monthsDisabled > 12) return 0;if (employee.isPartTime) return 0;After:
if (isNotEligibleForDisability(employee)) return 0;
function isNotEligibleForDisability(employee) { return employee.seniority < 2 || employee.monthsDisabled > 12 || employee.isPartTime;}Replace Nested Conditional with Guard Clauses
When to use: Deeply nested conditionals making flow hard to follow
Motivation: Use guard clauses for special cases, keeping normal flow clear.
Mechanics:
- Find the special case conditions
- Replace them with guard clauses that return early
- Test after each change
Before:
function payAmount(employee) { let result; if (employee.isSeparated) { result = { amount: 0, reasonCode: "SEP" }; } else { if (employee.isRetired) { result = { amount: 0, reasonCode: "RET" }; } else { result = calculateNormalPay(employee); } } return result;}After:
function payAmount(employee) { if (employee.isSeparated) return { amount: 0, reasonCode: "SEP" }; if (employee.isRetired) return { amount: 0, reasonCode: "RET" }; return calculateNormalPay(employee);}Replace Conditional with Polymorphism
When to use: Switch/case based on type, conditional logic varying by type
Motivation: Let objects handle their own behavior.
Mechanics:
- Create class hierarchy (if not exists)
- Use Factory Function for object creation
- Move conditional logic into superclass method
- Create subclass method for each case
- Remove original conditional
Before:
function plumages(birds) { return birds.map(b => plumage(b));}
function plumage(bird) { switch (bird.type) { case 'EuropeanSwallow': return "average"; case 'AfricanSwallow': return (bird.numberOfCoconuts > 2) ? "tired" : "average"; case 'NorwegianBlueParrot': return (bird.voltage > 100) ? "scorched" : "beautiful"; default: return "unknown"; }}After:
class Bird { get plumage() { return "unknown"; }}
class EuropeanSwallow extends Bird { get plumage() { return "average"; }}
class AfricanSwallow extends Bird { get plumage() { return (this.numberOfCoconuts > 2) ? "tired" : "average"; }}
class NorwegianBlueParrot extends Bird { get plumage() { return (this.voltage > 100) ? "scorched" : "beautiful"; }}
function createBird(data) { switch (data.type) { case 'EuropeanSwallow': return new EuropeanSwallow(data); case 'AfricanSwallow': return new AfricanSwallow(data); case 'NorwegianBlueParrot': return new NorwegianBlueParrot(data); default: return new Bird(data); }}Introduce Special Case (Null Object)
When to use: Repeated null checks for special cases
Motivation: Return a special object that handles the special case.
Mechanics:
- Create special case class with expected interface
- Add isSpecialCase check
- Introduce factory method
- Replace null checks with special case object usage
- Test
Before:
const customer = site.customer;// ... many places checkingif (customer === "unknown") { customerName = "occupant";} else { customerName = customer.name;}After:
class UnknownCustomer { get name() { return "occupant"; } get billingPlan() { return registry.defaultPlan; }}
// Factory methodfunction customer(site) { return site.customer === "unknown" ? new UnknownCustomer() : site.customer;}
// Usage - no null checks neededconst customerName = customer.name;Refactoring APIs
Separate Query from Modifier
When to use: Function both returns a value and has side effects
Motivation: Make it clear which operations have side effects.
Mechanics:
- Create a new query function
- Copy original function’s return logic
- Modify original to return void
- Replace calls that use return value
- Test
Before:
function alertForMiscreant(people) { for (const p of people) { if (p === "Don") { setOffAlarms(); return "Don"; } if (p === "John") { setOffAlarms(); return "John"; } } return "";}After:
function findMiscreant(people) { for (const p of people) { if (p === "Don") return "Don"; if (p === "John") return "John"; } return "";}
function alertForMiscreant(people) { if (findMiscreant(people) !== "") setOffAlarms();}Parameterize Function
When to use: Several functions doing similar things with different values
Motivation: Remove duplication by adding a parameter.
Mechanics:
- Select one function
- Add parameter for the varying literal
- Change body to use the parameter
- Test
- Change callers to use the parameterized version
- Remove now-unused functions
Before:
function tenPercentRaise(person) { person.salary = person.salary * 1.10;}
function fivePercentRaise(person) { person.salary = person.salary * 1.05;}After:
function raise(person, factor) { person.salary = person.salary * (1 + factor);}
// Usageraise(person, 0.10);raise(person, 0.05);Remove Flag Argument
When to use: Boolean parameter that changes function behavior
Motivation: Make the behavior explicit through separate functions.
Mechanics:
- Create explicit function for each flag value
- Replace each call with appropriate new function
- Test after each change
- Remove original function
Before:
function bookConcert(customer, isPremium) { if (isPremium) { // premium booking logic } else { // regular booking logic }}
bookConcert(customer, true);bookConcert(customer, false);After:
function bookPremiumConcert(customer) { // premium booking logic}
function bookRegularConcert(customer) { // regular booking logic}
bookPremiumConcert(customer);bookRegularConcert(customer);Dealing with Inheritance
Pull Up Method
When to use: Same method in multiple subclasses
Motivation: Remove duplication in class hierarchy.
Mechanics:
- Inspect methods to ensure they are identical
- Check signatures are the same
- Create new method in superclass
- Copy body from one subclass
- Delete one subclass method, test
- Delete other subclass methods, test each
Push Down Method
When to use: Behavior relevant only to a subset of subclasses
Motivation: Put method where it’s used.
Mechanics:
- Copy method to each subclass that needs it
- Remove method from superclass
- Test
- Remove from subclasses that don’t need it
- Test
Replace Subclass with Delegate
When to use: Inheritance is being used incorrectly, need more flexibility
Motivation: Prefer composition over inheritance when appropriate.
Mechanics:
- Create empty class for delegate
- Add field to host class holding delegate
- Create constructor for delegate, called from host
- Move features to delegate
- Test after each move
- Replace inheritance with delegation
Extract Class
When to use: Large class with multiple responsibilities
Motivation: Split class to maintain single responsibility.
Mechanics:
- Decide how to split responsibilities
- Create new class
- Move field from original to new class
- Test
- Move methods from original to new class
- Test after each move
- Review and rename both classes
- Decide how to expose new class
Before:
class Person { get name() { return this._name; } set name(arg) { this._name = arg; } get officeAreaCode() { return this._officeAreaCode; } set officeAreaCode(arg) { this._officeAreaCode = arg; } get officeNumber() { return this._officeNumber; } set officeNumber(arg) { this._officeNumber = arg; }
get telephoneNumber() { return `(${this._officeAreaCode}) ${this._officeNumber}`; }}After:
class Person { constructor() { this._telephoneNumber = new TelephoneNumber(); } get name() { return this._name; } set name(arg) { this._name = arg; } get telephoneNumber() { return this._telephoneNumber.toString(); } get officeAreaCode() { return this._telephoneNumber.areaCode; } set officeAreaCode(arg) { this._telephoneNumber.areaCode = arg; }}
class TelephoneNumber { get areaCode() { return this._areaCode; } set areaCode(arg) { this._areaCode = arg; } get number() { return this._number; } set number(arg) { this._number = arg; } toString() { return `(${this._areaCode}) ${this._number}`; }}Quick Reference: Smell to Refactoring
| Code Smell | Primary Refactoring | Alternative |
|---|---|---|
| Long Method | Extract Method | Replace Temp with Query |
| Duplicate Code | Extract Method | Pull Up Method |
| Large Class | Extract Class | Extract Subclass |
| Long Parameter List | Introduce Parameter Object | Preserve Whole Object |
| Feature Envy | Move Method | Extract Method + Move |
| Data Clumps | Extract Class | Introduce Parameter Object |
| Primitive Obsession | Replace Primitive with Object | Replace Type Code |
| Switch Statements | Replace Conditional with Polymorphism | Replace Type Code |
| Temporary Field | Extract Class | Introduce Null Object |
| Message Chains | Hide Delegate | Extract Method |
| Middle Man | Remove Middle Man | Inline Method |
| Divergent Change | Extract Class | Split Phase |
| Shotgun Surgery | Move Method | Inline Class |
| Dead Code | Remove Dead Code | - |
| Speculative Generality | Collapse Hierarchy | Inline Class |
Further Reading
- Fowler, M. (2018). Refactoring: Improving the Design of Existing Code (2nd ed.)
- Online catalog: https://refactoring.com/catalog/