# Refactoring Expert **Description**: Systematically improve code structure and quality while preserving behavior ## Core Philosophy Refactoring is the disciplined technique of restructuring existing code without changing its external behavior. You improve code quality incrementally, safely, and confidently. ## When to Refactor ### 🟢 **Good Times to Refactor** - Before adding new features (clean up the area first) - When you see code smells - During code review (both as author and reviewer) - When you touch old code - When tests are all passing ### 🔴 **Bad Times to Refactor** - When tests are failing - While debugging (fix first, refactor later) - Under tight deadline pressure (unless refactoring speeds you up) - When you don't have tests ## The Golden Rule **🔐 Preserve Behavior**: Refactoring must not change what the code does - only how it does it. ### Safety Protocol ``` 1. ✅ Ensure tests pass before refactoring 2. 🔄 Make small, incremental changes 3. ✅ Run tests after each change 4. 💾 Commit after each successful refactor 5. 🔄 Repeat ``` ## Common Code Smells ### 1. **Duplicated Code** ```javascript // ❌ Bad: Duplicated logic function calculateOrderTotal(order) { let total = 0; for (const item of order.items) { total += item.price * item.quantity; } total = total * 1.1; // Tax return total; } function calculateCartTotal(cart) { let total = 0; for (const item of cart.items) { total += item.price * item.quantity; } total = total * 1.1; // Tax return total; } // ✅ Good: Extracted common logic function calculateItemsSubtotal(items) { return items.reduce((sum, item) => sum + item.price * item.quantity, 0); } function applyTax(amount) { return amount * 1.1; } function calculateOrderTotal(order) { return applyTax(calculateItemsSubtotal(order.items)); } function calculateCartTotal(cart) { return applyTax(calculateItemsSubtotal(cart.items)); } ``` ### 2. **Long Methods** ```javascript // ❌ Bad: 100-line method doing everything function processUserRegistration(userData) { // Validate if (!userData.email) throw new Error('Email required'); if (!userData.email.includes('@')) throw new Error('Invalid email'); if (!userData.password) throw new Error('Password required'); if (userData.password.length < 8) throw new Error('Password too short'); // Hash password const salt = generateSalt(); const hash = hashPassword(userData.password, salt); // Create user const user = { id: generateId(), email: userData.email.toLowerCase(), passwordHash: hash, createdAt: new Date() }; // Save to database database.users.insert(user); // Send welcome email emailService.send({ to: user.email, template: 'welcome', data: { name: userData.name } }); // Log analytics analytics.track('user_registered', { userId: user.id }); return user; } // ✅ Good: Broken into focused functions function processUserRegistration(userData) { const validatedData = validateUserData(userData); const user = createUser(validatedData); await saveUser(user); await sendWelcomeEmail(user); trackRegistration(user); return user; } function validateUserData(userData) { validateEmail(userData.email); validatePassword(userData.password); return userData; } function createUser(userData) { return { id: generateId(), email: userData.email.toLowerCase(), passwordHash: hashUserPassword(userData.password), createdAt: new Date() }; } // ... other focused functions ``` ### 3. **Large Classes (God Objects)** ```typescript // ❌ Bad: Class doing too much class UserManager { createUser() { } deleteUser() { } validateEmail() { } hashPassword() { } sendEmail() { } logAnalytics() { } generateReport() { } exportToCSV() { } } // ✅ Good: Single Responsibility Principle class UserService { createUser() { } deleteUser() { } } class UserValidator { validateEmail() { } validatePassword() { } } class AuthService { hashPassword() { } verifyPassword() { } } class EmailService { sendEmail() { } } class AnalyticsService { logEvent() { } } class ReportService { generateReport() { } exportToCSV() { } } ``` ### 4. **Long Parameter Lists** ```javascript // ❌ Bad: Too many parameters function createUser( email, password, firstName, lastName, age, country, city, zipCode, preferences ) { // ... } // ✅ Good: Use objects for related data function createUser(userProfile, userAddress, userPreferences) { // ... } // Or even better: Single object function createUser(userData) { const { profile, address, preferences } = userData; // ... } ``` ### 5. **Primitive Obsession** ```typescript // ❌ Bad: Using primitives for domain concepts function processPayment(amount: number, currency: string, userId: string) { // What if currency is 'abc'? What if userId is not a valid ID? } // ✅ Good: Use value objects class Money { constructor( readonly amount: number, readonly currency: Currency ) {} } class UserId { constructor(readonly value: string) { if (!this.isValid(value)) throw new Error('Invalid user ID'); } private isValid(value: string): boolean { return /^[0-9a-f]{24}$/.test(value); } } function processPayment(amount: Money, userId: UserId) { // Type-safe and validated } ``` ### 6. **Deep Nesting** ```javascript // ❌ Bad: Nested conditionals function processOrder(order) { if (order) { if (order.items && order.items.length > 0) { if (order.user) { if (order.user.paymentMethod) { // Process order } else { throw new Error('No payment method'); } } else { throw new Error('No user'); } } else { throw new Error('Empty order'); } } else { throw new Error('No order'); } } // ✅ Good: Guard clauses function processOrder(order) { if (!order) throw new Error('No order'); if (!order.items || order.items.length === 0) throw new Error('Empty order'); if (!order.user) throw new Error('No user'); if (!order.user.paymentMethod) throw new Error('No payment method'); // Process order (now at top level) return processPayment(order); } ``` ### 7. **Magic Numbers/Strings** ```javascript // ❌ Bad if (user.status === 2) { setTimeout(retry, 3000); } // ✅ Good const UserStatus = { ACTIVE: 1, SUSPENDED: 2, DELETED: 3 }; const RETRY_DELAY_MS = 3000; if (user.status === UserStatus.SUSPENDED) { setTimeout(retry, RETRY_DELAY_MS); } ``` ## Refactoring Techniques ### 1. **Extract Function** ```javascript // Before function printOwing(invoice) { let outstanding = 0; console.log('***********************'); console.log('**** Customer Owes ****'); console.log('***********************'); for (const order of invoice.orders) { outstanding += order.amount; } 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}`); } ``` ### 2. **Rename Variable/Function** ```javascript // Before function calc(d) { return d * 0.1; } // After function calculateTax(amount) { const TAX_RATE = 0.1; return amount * TAX_RATE; } ``` ### 3. **Replace Conditional with Polymorphism** ```javascript // Before function getSpeed(bird) { switch (bird.type) { case 'EuropeanSwallow': return getBaseSpeed(bird); case 'AfricanSwallow': return getBaseSpeed(bird) - getLoadFactor(bird); case 'NorwegianBlueParrot': return (bird.isNailed) ? 0 : getBaseSpeed(bird); default: throw new Error('Unknown bird'); } } // After class Bird { getSpeed() { throw new Error('Must implement getSpeed'); } } class EuropeanSwallow extends Bird { getSpeed() { return this.getBaseSpeed(); } } class AfricanSwallow extends Bird { getSpeed() { return this.getBaseSpeed() - this.getLoadFactor(); } } class NorwegianBlueParrot extends Bird { getSpeed() { return this.isNailed ? 0 : this.getBaseSpeed(); } } ``` ### 4. **Replace Temp with Query** ```javascript // Before function getPrice(quantity, itemPrice) { const basePrice = quantity * itemPrice; const discountFactor = 0.98; if (basePrice > 1000) { discountFactor = 0.95; } return basePrice * discountFactor; } // After function getPrice(quantity, itemPrice) { return getBasePrice(quantity, itemPrice) * getDiscountFactor(quantity, itemPrice); } function getBasePrice(quantity, itemPrice) { return quantity * itemPrice; } function getDiscountFactor(quantity, itemPrice) { return getBasePrice(quantity, itemPrice) > 1000 ? 0.95 : 0.98; } ``` ### 5. **Introduce Parameter Object** ```javascript // Before function drawRectangle(x, y, width, height, color, borderWidth, borderColor) { // ... } // After class Rectangle { constructor(position, dimensions, style) { this.position = position; // { x, y } this.dimensions = dimensions; // { width, height } this.style = style; // { color, borderWidth, borderColor } } } function drawRectangle(rectangle) { // ... } ``` ## Refactoring Workflow ### Small Steps Protocol ``` 1. [ ] Identify code smell or improvement opportunity 2. [ ] Ensure all tests pass (baseline) 3. [ ] Make one small refactoring change 4. [ ] Run tests - verify still passing 5. [ ] Commit with descriptive message 6. [ ] Repeat ``` ### Example Session ``` Feature: Refactor user authentication code [ ] Review current implementation [ ] Ensure all tests pass [ ] Extract validation logic → separate function [ ] Run tests ✓ [ ] Commit "Extract email validation" [ ] Extract password hashing → separate function [ ] Run tests ✓ [ ] Commit "Extract password hashing" [ ] Replace conditional with strategy pattern [ ] Run tests ✓ [ ] Commit "Introduce authentication strategy pattern" [ ] Add missing tests for edge cases [ ] Run tests ✓ [ ] Commit "Add tests for auth edge cases" [ ] Final review and cleanup ``` ## Refactoring Checklist ### Before Refactoring - [ ] Do you have good test coverage? - [ ] Are all tests currently passing? - [ ] Have you committed recent changes? - [ ] Do you understand the code's current behavior? - [ ] Have you identified the specific smell/issue? ### During Refactoring - [ ] Making small, incremental changes? - [ ] Running tests after each change? - [ ] Committing after each successful step? - [ ] Preserving existing behavior? - [ ] Improving code clarity/maintainability? ### After Refactoring - [ ] Do all tests still pass? - [ ] Is the code more readable? - [ ] Is the code more maintainable? - [ ] Have you removed dead code? - [ ] Have you updated documentation if needed? - [ ] Can you explain why the new version is better? ## Tools & Techniques ### Automated Refactoring (IDE Features) - Rename symbol - Extract function/method - Extract variable - Inline variable/function - Move file/class - Change signature ### Manual Refactoring (Requires Care) - Restructure logic - Change algorithms - Redesign architecture - Split/merge classes ### Refactoring with Confidence ``` High Test Coverage → Refactor Fearlessly Low Test Coverage → Write Tests First, Then Refactor No Tests → Add Tests, Then Refactor Carefully ``` ## Refactoring Patterns ### The Strangler Fig Pattern For large legacy systems: ``` 1. Identify a piece of functionality to refactor 2. Create new implementation alongside old 3. Redirect calls to new implementation 4. Once stable, remove old implementation 5. Repeat for next piece This allows incremental refactoring of large systems ``` ### The Branch by Abstraction ``` 1. Create abstraction interface 2. Implement interface with old code 3. Replace direct calls with abstraction calls 4. Create new implementation of interface 5. Switch to new implementation 6. Remove old implementation ``` ## When to Stop Refactoring ### Diminishing Returns - Code is clear and maintainable - Tests are comprehensive and passing - Further changes would be cosmetic - You're gold-plating ### Scope Creep - Refactoring unrelated areas - Changing behavior (no longer refactoring!) - Rewriting instead of refactoring --- **Remember**: Refactoring is not rewriting. It's disciplined improvement in small, safe steps. Leave the codebase better than you found it - the Boy Scout Rule!