Code Refactoring Patterns
A comprehensive guide to refactoring code systematically while maintaining functionality and improving quality.
When to Refactor
-
Code smells detected (duplicated code, long functions, etc.)
-
Before adding new features to complex areas
-
After understanding improves ("now I see a better way")
-
When tests are in place
-
Performance optimization needed
Refactoring Rules
-
Never refactor without tests: Write tests first if they don't exist
-
Small steps: Make one change at a time
-
Run tests after each change: Ensure nothing breaks
-
Commit often: Each working refactor is a commit
-
Don't mix refactoring with feature work: Separate concerns
Common Code Smells
- Long Method/Function
Smell: Functions over 20-30 lines
Refactor: Extract Method
// Before function processOrder(order: Order) { // Validate order (10 lines) // Calculate totals (15 lines) // Apply discounts (12 lines) // Send confirmation (8 lines) }
// After function processOrder(order: Order) { validateOrder(order); const totals = calculateTotals(order); const finalPrice = applyDiscounts(totals, order); sendConfirmation(order, finalPrice); }
- Duplicated Code
Smell: Same code in multiple places
Refactor: Extract Function/Class
// Before
function formatUserName(user: User) {
return ${user.firstName} ${user.lastName};
}
function formatAuthorName(author: Author) {
return ${author.firstName} ${author.lastName};
}
// After
function formatFullName(person: { firstName: string; lastName: string }) {
return ${person.firstName} ${person.lastName};
}
- Long Parameter List
Smell: Functions with 4+ parameters
Refactor: Parameter Object
// Before function createUser( firstName: string, lastName: string, email: string, phone: string, address: string ) { }
// After interface UserDetails { firstName: string; lastName: string; email: string; phone: string; address: string; }
function createUser(details: UserDetails) { }
- Large Class
Smell: Classes with many responsibilities
Refactor: Extract Class
// Before class UserManager { createUser() { } deleteUser() { } sendEmail() { } generateReport() { } logActivity() { } }
// After class UserService { createUser() { } deleteUser() { } }
class EmailService { sendEmail() { } }
class ReportService { generateReport() { } }
- Feature Envy
Smell: Method uses data from another class more than its own
Refactor: Move Method
// Before class Order { calculate() { return this.customer.getDiscount() * this.amount; } }
// After class Customer { calculateOrderAmount(order: Order) { return this.getDiscount() * order.amount; } }
Refactoring Techniques
Extract Method
Break large functions into smaller, named pieces:
// Before
function renderUser(user: User) {
console.log(<div>);
console.log( <h1>${user.firstName} ${user.lastName}</h1>);
console.log( <p>${user.email}</p>);
console.log(</div>);
}
// After
function renderUser(user: User) {
console.log(<div>);
console.log( ${renderUserHeader(user)});
console.log( ${renderUserEmail(user)});
console.log(</div>);
}
function renderUserHeader(user: User) {
return <h1>${user.firstName} ${user.lastName}</h1>;
}
function renderUserEmail(user: User) {
return <p>${user.email}</p>;
}
Rename for Clarity
Use descriptive names:
// Before function calc(a: number, b: number) { return a * b * 0.08; }
// After function calculateSalesTax(amount: number, quantity: number) { const TAX_RATE = 0.08; return amount * quantity * TAX_RATE; }
Introduce Explaining Variable
Make complex expressions clear:
// Before if (platform.toUpperCase().includes('MAC') && browser.toUpperCase().includes('IE') && wasInitialized() && resized) { // do something }
// After const isMacOS = platform.toUpperCase().includes('MAC'); const isIE = browser.toUpperCase().includes('IE'); const wasResized = wasInitialized() && resized;
if (isMacOS && isIE && wasResized) { // do something }
Replace Conditional with Polymorphism
Use inheritance/interfaces instead of switch/if-else chains:
// Before function getSpeed(vehicle: Vehicle) { switch (vehicle.type) { case 'car': return vehicle.speed * 1.0; case 'bike': return vehicle.speed * 0.8; case 'truck': return vehicle.speed * 0.6; } }
// After interface Vehicle { getSpeed(): number; }
class Car implements Vehicle { getSpeed() { return this.speed * 1.0; } }
class Bike implements Vehicle { getSpeed() { return this.speed * 0.8; } }
Simplify Conditional Logic
Use early returns and guard clauses:
// Before function processPayment(payment: Payment) { if (payment.isValid()) { if (payment.amount > 0) { if (payment.method === 'card') { // process card payment } else { // invalid method } } else { // invalid amount } } else { // invalid payment } }
// After function processPayment(payment: Payment) { if (!payment.isValid()) { throw new Error('Invalid payment'); }
if (payment.amount <= 0) { throw new Error('Invalid amount'); }
if (payment.method !== 'card') { throw new Error('Invalid method'); }
// process card payment }
Refactoring Workflow
Step 1: Understand Current Code
Read the code thoroughly
cat src/feature.ts
Check tests
cat src/feature.test.ts
Find all usages
grep -r "functionName" src/
Step 2: Ensure Tests Exist
Run existing tests
npm test src/feature.test.ts
Add missing tests if needed
Step 3: Refactor in Small Steps
Make one refactoring change
Run tests
npm test
Commit if tests pass
git add . && git commit -m "refactor: extract method calculateTotal"
Repeat for next refactoring
Step 4: Verify No Regression
Run full test suite
npm test
Check type errors
npx tsc --noEmit
Verify lint
npm run lint
Step 5: Performance Check
Compare before/after if performance-critical
npm run benchmark
Refactoring Checklist
Before refactoring:
-
Tests exist and pass
-
Understand current behavior
-
Know why refactoring is needed
-
Have time to complete refactoring
During refactoring:
-
Make one change at a time
-
Run tests after each change
-
Keep commits small and focused
-
Don't add features during refactoring
After refactoring:
-
All tests pass
-
No type errors
-
Lint passes
-
Code review completed
-
Documentation updated if needed
Integration Points
Complements:
-
verification-loop: For validation after refactoring
-
tdd-workflow: For test-first approach
-
coding-standards-enforcer: For style consistency
-
testing-patterns: For test design
Refactoring Anti-Patterns
❌ Don't:
-
Refactor without tests
-
Mix refactoring with feature work
-
Make large changes at once
-
Refactor code you don't understand
-
Skip verification steps
✅ Do:
-
Write tests first
-
Separate refactoring commits
-
Make incremental changes
-
Understand code before refactoring
-
Run tests frequently