| name | refactoring-patterns |
| description | Safe refactoring techniques — extract method, rename, move, inline, and structural patterns. Includes code smell identification and transformation recipes. Use when refactoring code or improving structure. |
Refactoring Patterns
Safety Rules
Before any refactoring, verify all of the following:
Code Smell Catalog
| Smell | Symptoms | Typical Refactoring |
|---|
| Long Method | Method over 20 lines; multiple levels of abstraction | Extract Method |
| Large Class | Class over 300 lines; too many responsibilities | Extract Class |
| Feature Envy | Method uses more data from another class than its own | Move Method |
| Data Clump | Same group of fields/params appear together repeatedly | Introduce Parameter Object |
| Primitive Obsession | Using primitives instead of small domain objects | Replace Primitive with Value Object |
| Shotgun Surgery | One change requires edits across many files | Move Method, Inline Class |
| Divergent Change | One class changed for many different reasons | Extract Class |
| Dead Code | Unreachable code, unused variables, commented-out blocks | Remove Dead Code |
| Long Parameter List | Method takes more than 3 parameters | Introduce Parameter Object |
| Switch Statements | Repeated switch/if-else on the same type field | Replace Conditional with Polymorphism |
| Duplicate Code | Same logic in multiple places | Extract Method, Extract Superclass |
| Magic Numbers | Unexplained literal values in logic | Replace Magic Number with Named Constant |
Smell Examples
Long Method:
function processOrder(order: Order) {
}
Feature Envy:
class Order {
calculateShipping() {
if (this.customer.address.country === "US") {
if (this.customer.tier === "premium") return 0;
return this.customer.address.isRemote ? 15 : 5;
}
return 25;
}
}
Data Clump:
function createUser(name: string, street: string, city: string, state: string, zip: string) {}
function updateAddress(street: string, city: string, state: string, zip: string) {}
Named Refactorings
| Refactoring | Input | Output |
|---|
| Extract Method | Code block inside a method | New method + call site |
| Extract Class | Fields and methods from a class | New class with focused responsibility |
| Inline Method | Trivial method | Body placed at all call sites |
| Rename | Unclear name | Intention-revealing name |
| Move Method | Method in wrong class | Method in the class that owns the data |
| Introduce Parameter Object | Multiple related parameters | Single object parameter |
| Replace Conditional with Polymorphism | Type-based switch/if-else | Subclasses with overridden method |
| Replace Magic Number with Named Constant | Literal number in code | Named constant |
| Decompose Conditional | Complex boolean expression | Named methods for conditions |
| Remove Dead Code | Unused code | Deletion |
Extract Method
Recipe: (1) Identify code to extract. (2) Create method with intention-revealing name. (3) Copy code in. (4) Local variables become locals; outer scope reads become parameters; modified-and-used-after become return values. (5) Replace original with call. (6) Run tests.
function printInvoice(invoice: Invoice) {
console.log("=== Invoice ===");
let total = 0;
for (const item of invoice.items) { total += item.price * item.quantity; }
const tax = total * 0.08;
console.log(`Total: ${total + tax}`);
}
function printInvoice(invoice: Invoice) {
console.log("=== Invoice ===");
console.log(`Total: ${calculateGrandTotal(invoice.items)}`);
}
function calculateGrandTotal(items: InvoiceItem[]): number {
const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
return subtotal + subtotal * 0.08;
}
Extract Class
Recipe: (1) Identify cohesive fields and methods. (2) Create new class. (3) Move fields. (4) Move methods. (5) Delegate from original. (6) Run tests after each move.
class User {
name: string; email: string;
street: string; city: string; state: string; zip: string;
fullAddress(): string { return `${this.street}, ${this.city}, ${this.state} ${this.zip}`; }
}
class Address {
constructor(public street: string, public city: string, public state: string, public zip: string) {}
full(): string { return `${this.street}, ${this.city}, ${this.state} ${this.zip}`; }
}
class {
: ; : ; : ;
}
Introduce Parameter Object
Recipe: (1) Create type for the parameter group. (2) Add object parameter. (3) Update callers. (4) Remove old params. (5) Run tests.
function searchProducts(minPrice: number, maxPrice: number, category: string, inStock: boolean) {}
interface ProductFilter { minPrice: number; maxPrice: number; category: string; inStock: boolean; }
function searchProducts(filter: ProductFilter) {}
Replace Conditional with Polymorphism
Recipe: (1) Create base interface with the varying method. (2) Create subclass per case. (3) Move case logic into subclass. (4) Replace conditional with method call. (5) Run tests.
function calculateArea(shape: Shape): number {
switch (shape.type) {
case "circle": return Math.PI * shape.radius ** 2;
case "rectangle": return shape.width * shape.height;
case "triangle": return (shape.base * shape.height) / 2;
}
}
interface Shape { area(): number; }
class Circle implements Shape {
constructor(private radius: number) {}
area() { return Math.PI * this.radius ** 2; }
}
class Rectangle implements Shape {
constructor( : , : ) {}
() { . * .; }
}
{
() {}
() { (. * .) / ; }
}
Decompose Conditional
Recipe: (1) Extract condition into a named method. (2) Extract then-branch if non-trivial. (3) Extract else-branch if non-trivial. (4) Run tests.
if (date.getMonth() >= 5 && date.getMonth() <= 8 && !isHoliday(date)) {
rate = quantity * summerRate;
} else {
rate = quantity * regularRate + serviceFee;
}
if (isSummerBusinessDay(date)) { rate = summerCharge(quantity); }
else { rate = regularCharge(quantity); }
Remove Dead Code
Recipe: (1) Verify truly unused (search references, check exports). (2) Delete. (3) Run tests. (4) Commit as refactor: Remove Dead Code -- <what>.
Dead code includes: unreachable branches, unused functions, commented-out blocks, unused variables and imports, removed feature flag code. Never comment out code "for later" -- version control preserves history.
Strangler Fig Pattern
For large-scale refactors that cannot be completed in a single step:
- Identify the boundary of the legacy code to replace
- Build the new implementation alongside the old one
- Route traffic incrementally using a facade or feature flag
- Verify each increment in production
- Remove the old implementation once all traffic uses the new code
class PaymentProcessor {
process(payment: Payment): Result { return this.legacyProcessor.process(payment); }
}
class PaymentProcessor {
process(payment: Payment): Result {
if (featureFlag.isEnabled("new-payment-processor"))
return this.newProcessor.process(payment);
return this.legacyProcessor.process(payment);
}
}
class PaymentProcessor {
process(payment: Payment): Result { return this.newProcessor.process(payment); }
}
Strangler Fig Checklist
Refactoring Workflow Summary
1. Identify the smell
2. Choose the named refactoring
3. Verify tests pass (write them if missing)
4. Apply one transformation
5. Run tests
6. Commit: "refactor: <Refactoring Name> -- <target>"
7. Repeat from step 4 until the smell is resolved