Refactoring - Improving Code Structure Without Changing Behavior
When to use this skill
- •Reducing technical debt in existing codebases
- •Extracting functions from long methods
- •Removing code duplication (DRY principle)
- •Simplifying complex conditional logic
- •Renaming variables, functions for clarity
- •Applying design patterns to improve structure
- •Improving code organization and file structure
- •Reducing coupling between modules
- •Increasing cohesion within classes/modules
- •Maintaining test coverage during refactoring
- •Breaking up large files or functions
- •Modernizing legacy code incrementally
When to use this skill
- •Code works but is difficult to understand, modify, or test. Improving structure while maintaining functionality.
- •When working on related tasks or features
- •During development that requires this expertise
Use when: Code works but is difficult to understand, modify, or test. Improving structure while maintaining functionality.
Core Principles
- •Behavior Preservation - Refactoring NEVER changes what the code does, only how
- •Small Steps - Make tiny changes, test after each step
- •Tests First - Ensure comprehensive tests exist before refactoring
- •One Thing at a Time - Don't mix refactoring with feature additions
- •Refactor When Adding Features - Leave code better than you found it
When to Refactor
Triggers
- •Before adding a feature - Make room for new functionality
- •During code review - Found code smells or confusing sections
- •When fixing bugs - Make bug impossible through better structure
- •Boy Scout Rule - Always leave code cleaner than you found it
Red Flags (Code Smells)
code
✗ Functions longer than ~50 lines ✗ Deeply nested conditionals (>3 levels) ✗ Duplicate code (copy-paste programming) ✗ Unclear variable names (x, temp, data) ✗ Functions with >3 parameters ✗ God classes (classes doing too much) ✗ Long parameter lists ✗ Comments explaining what code does (code should be self-documenting)
Refactoring Catalog
1. Extract Method/Function
When: Function does too many things or has complex logic
typescript
// Before - hard to understand
function processOrder(order) {
// Calculate total
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
if (item.discount) {
total -= item.price * item.quantity * item.discount;
}
}
// Apply taxes
const tax = total * 0.08;
total += tax;
// Check inventory
for (const item of order.items) {
const stock = inventory.get(item.id);
if (stock < item.quantity) {
throw new Error('Insufficient stock');
}
}
return { total, tax };
}
// After - clear responsibilities
function processOrder(order) {
validateInventory(order.items);
const subtotal = calculateSubtotal(order.items);
const tax = calculateTax(subtotal);
const total = subtotal + tax;
return { total, tax };
}
function calculateSubtotal(items) {
return items.reduce((sum, item) => {
const itemTotal = item.price * item.quantity;
const discount = item.discount ? itemTotal * item.discount : 0;
return sum + itemTotal - discount;
}, 0);
}
function calculateTax(amount) {
const TAX_RATE = 0.08;
return amount * TAX_RATE;
}
function validateInventory(items) {
for (const item of items) {
const stock = inventory.get(item.id);
if (stock < item.quantity) {
throw new InsufficientStockError(item.id, stock, item.quantity);
}
}
}
2. Replace Magic Numbers with Named Constants
typescript
// Before - what do these numbers mean?
if (user.age >= 18 && user.age <= 65) {
premium = basePrice * 1.0;
} else {
premium = basePrice * 1.5;
}
setTimeout(checkStatus, 60000);
// After - self-documenting
const MIN_STANDARD_AGE = 18;
const MAX_STANDARD_AGE = 65;
const STANDARD_RATE_MULTIPLIER = 1.0;
const HIGH_RISK_RATE_MULTIPLIER = 1.5;
const STATUS_CHECK_INTERVAL_MS = 60 * 1000; // 1 minute
if (user.age >= MIN_STANDARD_AGE && user.age <= MAX_STANDARD_AGE) {
premium = basePrice * STANDARD_RATE_MULTIPLIER;
} else {
premium = basePrice * HIGH_RISK_RATE_MULTIPLIER;
}
setTimeout(checkStatus, STATUS_CHECK_INTERVAL_MS);
3. Simplify Conditional Expressions
typescript
// Before - complex nested conditions
function getShippingCost(order) {
if (order.items.length > 0) {
if (order.total > 50) {
if (order.isPremium) {
return 0;
} else {
return 5;
}
} else {
if (order.isPremium) {
return 5;
} else {
return 10;
}
}
} else {
return 0;
}
}
// After - guard clauses and early returns
function getShippingCost(order) {
if (order.items.length === 0) return 0;
if (order.isPremium && order.total > 50) return 0;
if (order.isPremium) return 5;
if (order.total > 50) return 5;
return 10;
}
// Even better - strategy pattern
const SHIPPING_RATES = {
premiumOverFifty: { cost: 0, applies: (o) => o.isPremium && o.total > 50 },
premium: { cost: 5, applies: (o) => o.isPremium },
standardOverFifty: { cost: 5, applies: (o) => o.total > 50 },
standard: { cost: 10, applies: () => true }
};
function getShippingCost(order) {
if (order.items.length === 0) return 0;
for (const rate of Object.values(SHIPPING_RATES)) {
if (rate.applies(order)) return rate.cost;
}
}
4. Extract Variable for Clarity
typescript
// Before - hard to understand
if (
(platform === 'ios' && version >= 13) ||
(platform === 'android' && version >= 10) ||
(platform === 'web' && browserVersion >= 90)
) {
enableNewUI();
}
// After - intention-revealing names
const isIosSupportedVersion = platform === 'ios' && version >= 13;
const isAndroidSupportedVersion = platform === 'android' && version >= 10;
const isWebSupportedVersion = platform === 'web' && browserVersion >= 90;
const supportsNewUI =
isIosSupportedVersion ||
isAndroidSupportedVersion ||
isWebSupportedVersion;
if (supportsNewUI) {
enableNewUI();
}
5. Remove Dead Code
typescript
// Before - cluttered with unused code
function calculatePrice(item) {
let price = item.basePrice;
// Legacy discount system (no longer used)
// if (item.category === 'electronics') {
// price *= 0.9;
// }
// Apply current discount
if (item.discount) {
price *= (1 - item.discount);
}
// Old tax calculation
// const oldTax = price * 0.05;
// New tax calculation
const tax = price * 0.08;
return price + tax;
}
// After - clean and focused
function calculatePrice(item) {
let price = item.basePrice;
if (item.discount) {
price *= (1 - item.discount);
}
const TAX_RATE = 0.08;
const tax = price * TAX_RATE;
return price + tax;
}
6. Replace Nested Conditionals with Guard Clauses
typescript
// Before - deeply nested
function withdraw(account, amount) {
if (account.isActive) {
if (account.balance >= amount) {
if (amount > 0) {
if (!account.isFrozen) {
account.balance -= amount;
return { success: true, newBalance: account.balance };
} else {
return { success: false, error: 'Account frozen' };
}
} else {
return { success: false, error: 'Invalid amount' };
}
} else {
return { success: false, error: 'Insufficient funds' };
}
} else {
return { success: false, error: 'Account inactive' };
}
}
// After - guard clauses
function withdraw(account, amount) {
if (!account.isActive) {
return { success: false, error: 'Account inactive' };
}
if (account.isFrozen) {
return { success: false, error: 'Account frozen' };
}
if (amount <= 0) {
return { success: false, error: 'Invalid amount' };
}
if (account.balance < amount) {
return { success: false, error: 'Insufficient funds' };
}
account.balance -= amount;
return { success: true, newBalance: account.balance };
}
7. Replace Type Code with Polymorphism
typescript
// Before - type checking everywhere
class Employee {
constructor(type, salary) {
this.type = type; // 'engineer', 'manager', 'salesperson'
this.salary = salary;
}
calculateBonus() {
if (this.type === 'engineer') {
return this.salary * 0.1;
} else if (this.type === 'manager') {
return this.salary * 0.2;
} else if (this.type === 'salesperson') {
return this.salary * 0.15;
}
}
getTitle() {
if (this.type === 'engineer') {
return 'Software Engineer';
} else if (this.type === 'manager') {
return 'Engineering Manager';
} else if (this.type === 'salesperson') {
return 'Sales Representative';
}
}
}
// After - polymorphism
class Employee {
constructor(salary) {
this.salary = salary;
}
calculateBonus() {
throw new Error('Must be implemented by subclass');
}
getTitle() {
throw new Error('Must be implemented by subclass');
}
}
class Engineer extends Employee {
calculateBonus() {
return this.salary * 0.1;
}
getTitle() {
return 'Software Engineer';
}
}
class Manager extends Employee {
calculateBonus() {
return this.salary * 0.2;
}
getTitle() {
return 'Engineering Manager';
}
}
class Salesperson extends Employee {
calculateBonus() {
return this.salary * 0.15;
}
getTitle() {
return 'Sales Representative';
}
}
8. Consolidate Duplicate Code
typescript
// Before - repeated logic
function calculateEmployeeBonus(employee) {
let bonus = employee.salary * 0.1;
if (employee.yearsOfService > 5) {
bonus += 1000;
}
if (employee.hasTopPerformance) {
bonus *= 1.5;
}
return bonus;
}
function calculateContractorBonus(contractor) {
let bonus = contractor.salary * 0.1;
if (contractor.yearsOfService > 5) {
bonus += 1000;
}
if (contractor.hasTopPerformance) {
bonus *= 1.5;
}
return bonus;
}
// After - shared logic
function calculateBonus(person) {
let bonus = person.salary * 0.1;
if (person.yearsOfService > 5) {
bonus += 1000;
}
if (person.hasTopPerformance) {
bonus *= 1.5;
}
return bonus;
}
// Use for both
const employeeBonus = calculateBonus(employee);
const contractorBonus = calculateBonus(contractor);
Refactoring Process
Step-by-Step Approach
code
1. Identify smell or improvement opportunity 2. Ensure tests exist and pass 3. Make ONE small refactoring change 4. Run tests - must still pass 5. Commit (optional but recommended) 6. Repeat steps 3-5 until satisfied 7. Final test run 8. Code review
Example Process
typescript
// Original code
function process(data) {
let result = [];
for (let i = 0; i < data.length; i++) {
if (data[i].status === 'active' && data[i].age >= 18) {
result.push({
id: data[i].id,
name: data[i].name,
email: data[i].email
});
}
}
return result;
}
// Step 1: Extract condition
function isEligible(item) {
return item.status === 'active' && item.age >= 18;
}
function process(data) {
let result = [];
for (let i = 0; i < data.length; i++) {
if (isEligible(data[i])) {
result.push({
id: data[i].id,
name: data[i].name,
email: data[i].email
});
}
}
return result;
}
// Test ✓
// Step 2: Replace loop with array method
function process(data) {
let result = [];
data.forEach(item => {
if (isEligible(item)) {
result.push({
id: item.id,
name: item.name,
email: item.email
});
}
});
return result;
}
// Test ✓
// Step 3: Extract transformation
function transformToUser(item) {
return {
id: item.id,
name: item.name,
email: item.email
};
}
function process(data) {
let result = [];
data.forEach(item => {
if (isEligible(item)) {
result.push(transformToUser(item));
}
});
return result;
}
// Test ✓
// Step 4: Use filter and map
function process(data) {
return data
.filter(isEligible)
.map(transformToUser);
}
// Test ✓
// Final result - clear, functional, testable
Common Pitfalls
❌ Refactoring Without Tests
code
If tests don't exist, write them first! Otherwise, you can't verify behavior is preserved.
❌ Big Bang Refactoring
code
Don't rewrite everything at once. Small, incremental changes are safer and easier to review.
❌ Mixing Refactoring with Features
code
Separate commits: - Commit 1: Refactor (no behavior change) - Commit 2: Add feature (uses refactored code)
❌ Over-Engineering
code
Don't add complexity "for future needs" Refactor when needed, not speculatively. YAGNI: You Aren't Gonna Need It
Tools
IDE Refactoring Tools
code
✓ Rename (safe rename across all files) ✓ Extract method/function ✓ Extract variable ✓ Inline variable/function ✓ Change signature ✓ Move to file/module
Static Analysis
bash
# JavaScript/TypeScript eslint --fix prettier --write # Python pylint black # Find code smells # PMD, SonarQube, CodeClimate
Resources
- •Refactoring by Martin Fowler
- •Refactoring Catalog
- •Clean Code by Robert Martin
- •Working Effectively with Legacy Code by Michael Feathers
Remember: Refactoring is continuous improvement. Every time you touch code, leave it a little cleaner. Small, frequent refactorings are better than rare, massive rewrites.