Skip to Content
SkillsRefactorReferencesRefactoring Catalog

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

  1. Identify the smell using the code smells reference
  2. Find the matching refactoring in this catalog
  3. Follow the mechanics step by step
  4. 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:

  1. Create a new method named for what it does (not how)
  2. Copy the code fragment into the new method
  3. Scan for local variables used in the fragment
  4. Pass local variables as parameters (or declare in method)
  5. Handle return values appropriately
  6. Replace the original fragment with a call to the new method
  7. 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:

  1. Check that the method isn’t polymorphic
  2. Find all calls to the method
  3. Replace each call with the method body
  4. Test after each replacement
  5. 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:

  1. Ensure the expression has no side effects
  2. Declare an immutable variable
  3. Set it to the result of the expression (or part)
  4. Replace the original expression with the variable
  5. 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:

  1. Check that the right-hand side has no side effects
  2. If variable isn’t immutable, make it so and test
  3. Find the first reference and replace with the expression
  4. Test
  5. Repeat for all references
  6. Remove the declaration and assignment
  7. Test

Rename Variable

When to use: Name doesn’t clearly communicate purpose

Motivation: Good names are crucial for clean code.

Mechanics:

  1. If variable is widely used, consider encapsulating
  2. Find all references
  3. Change each reference
  4. Test

Tips:

  • Use intention-revealing names
  • Avoid abbreviations
  • Use domain terminology
// Bad const d = 30; const x = users.filter(u => u.a); // Good const 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):

  1. Remove parameters not needed
  2. Change the name
  3. Add parameters needed
  4. Test

Mechanics (Migration - for complex changes):

  1. If removing parameter, make sure it’s not used
  2. Create new function with desired declaration
  3. Have old function call new function
  4. Test
  5. Change callers to use new function
  6. Test after each
  7. 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:

  1. Create getter and setter functions
  2. Find all references
  3. Replace reads with getter
  4. Replace writes with setter
  5. Test after each change
  6. Restrict visibility of the variable

Before:

let defaultOwner = { firstName: "Martin", lastName: "Fowler" }; // Used in many places spaceship.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:

  1. Create a new class/structure for the grouped parameters
  2. Test
  3. Use Change Function Declaration to add the new object
  4. Test
  5. For each parameter in the group, remove it from the function and use the new object
  6. 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:

  1. Apply Encapsulate Record to the common data
  2. Move each function into the class
  3. Test after each move
  4. 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:

  1. Create a second function for the second phase
  2. Test
  3. Introduce an intermediate data structure between phases
  4. Test
  5. Extract first phase into its own function
  6. 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:

  1. Examine all program elements used by method in its class
  2. Check if method is polymorphic
  3. Copy method to target class
  4. Adjust for new context
  5. Make original method delegate to target
  6. Test
  7. 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:

  1. Encapsulate the field if not already
  2. Test
  3. Create field in target
  4. Update references to use target field
  5. Test
  6. 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:

  1. Extract the repeated code into a function if not already
  2. Move statements into that function
  3. Test
  4. 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:

  1. Use Extract Method on the code to move
  2. Use Inline Method on the original function
  3. Remove the now-inlined call
  4. Move extracted code to each caller
  5. 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:

  1. Apply Encapsulate Variable
  2. Create a simple value class
  3. Change the setter to create a new instance
  4. Change the getter to return the value
  5. Test
  6. Add richer behavior to the new class

Before:

class Order { constructor(data) { this.priority = data.priority; // string: "high", "rush", etc. } } // Usage if (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); } } // Usage if (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:

  1. Check that the variable is assigned only once
  2. Extract the assignment’s right-hand side into a method
  3. Replace references to the temp with the method call
  4. Test
  5. 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 method if (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:

  1. Apply Extract Method on the condition
  2. Apply Extract Method on the then-branch
  3. 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:

  1. Verify no side effects in conditions
  2. Combine conditions using and or or
  3. 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:

  1. Find the special case conditions
  2. Replace them with guard clauses that return early
  3. 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:

  1. Create class hierarchy (if not exists)
  2. Use Factory Function for object creation
  3. Move conditional logic into superclass method
  4. Create subclass method for each case
  5. 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:

  1. Create special case class with expected interface
  2. Add isSpecialCase check
  3. Introduce factory method
  4. Replace null checks with special case object usage
  5. Test

Before:

const customer = site.customer; // ... many places checking if (customer === "unknown") { customerName = "occupant"; } else { customerName = customer.name; }

After:

class UnknownCustomer { get name() { return "occupant"; } get billingPlan() { return registry.defaultPlan; } } // Factory method function customer(site) { return site.customer === "unknown" ? new UnknownCustomer() : site.customer; } // Usage - no null checks needed const 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:

  1. Create a new query function
  2. Copy original function’s return logic
  3. Modify original to return void
  4. Replace calls that use return value
  5. 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:

  1. Select one function
  2. Add parameter for the varying literal
  3. Change body to use the parameter
  4. Test
  5. Change callers to use the parameterized version
  6. 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); } // Usage raise(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:

  1. Create explicit function for each flag value
  2. Replace each call with appropriate new function
  3. Test after each change
  4. 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:

  1. Inspect methods to ensure they are identical
  2. Check signatures are the same
  3. Create new method in superclass
  4. Copy body from one subclass
  5. Delete one subclass method, test
  6. 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:

  1. Copy method to each subclass that needs it
  2. Remove method from superclass
  3. Test
  4. Remove from subclasses that don’t need it
  5. Test

Replace Subclass with Delegate

When to use: Inheritance is being used incorrectly, need more flexibility

Motivation: Prefer composition over inheritance when appropriate.

Mechanics:

  1. Create empty class for delegate
  2. Add field to host class holding delegate
  3. Create constructor for delegate, called from host
  4. Move features to delegate
  5. Test after each move
  6. Replace inheritance with delegation

Extract Class

When to use: Large class with multiple responsibilities

Motivation: Split class to maintain single responsibility.

Mechanics:

  1. Decide how to split responsibilities
  2. Create new class
  3. Move field from original to new class
  4. Test
  5. Move methods from original to new class
  6. Test after each move
  7. Review and rename both classes
  8. 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 SmellPrimary RefactoringAlternative
Long MethodExtract MethodReplace Temp with Query
Duplicate CodeExtract MethodPull Up Method
Large ClassExtract ClassExtract Subclass
Long Parameter ListIntroduce Parameter ObjectPreserve Whole Object
Feature EnvyMove MethodExtract Method + Move
Data ClumpsExtract ClassIntroduce Parameter Object
Primitive ObsessionReplace Primitive with ObjectReplace Type Code
Switch StatementsReplace Conditional with PolymorphismReplace Type Code
Temporary FieldExtract ClassIntroduce Null Object
Message ChainsHide DelegateExtract Method
Middle ManRemove Middle ManInline Method
Divergent ChangeExtract ClassSplit Phase
Shotgun SurgeryMove MethodInline Class
Dead CodeRemove Dead Code-
Speculative GeneralityCollapse HierarchyInline Class

Further Reading

Last updated on