Home / Notebooks / Software Engineering
Software Engineering
intermediate

Refactoring Essentials

Essential refactoring techniques and patterns for improving code quality

March 10, 2024
•
Updated regularly

Refactoring Essentials

Quick reference guide for refactoring code effectively.

What is Refactoring?

Refactoring is improving code structure without changing its behavior:

  • Improve readability and maintainability
  • Reduce complexity and technical debt
  • Make code easier to understand and modify
  • Enable future changes with less effort
  • Keep tests passing throughout the process
  • > "Refactoring is a disciplined technique for restructuring existing code, altering its internal structure without changing its external behavior." - Martin Fowler

    When to Refactor

    The Rule of Three

  • First time: Just do it
  • Second time: Duplicate with a wince
  • Third time: Refactor
  • Signs You Need to Refactor

  • Adding a feature takes too long
  • Code is hard to understand
  • Bugs keep appearing in the same area
  • Tests are difficult to write
  • Code reviews take too long
  • Code Smells

    Recognize patterns that indicate problems:

    Bloaters

    Long Method

    // ❌ BAD: Method doing too much
    function processOrder(order) {
      // Validate order (20 lines)
      if (!order.items) throw new Error('No items');
      if (order.items.length === 0) throw new Error('Empty order');
      // ... more validation
      
      // Calculate totals (30 lines)
      let subtotal = 0;
      for (let item of order.items) {
        subtotal += item.price * item.quantity;
      }
      // ... more calculation
      
      // Apply discounts (25 lines)
      let discount = 0;
      if (order.couponCode) {
        // ... discount logic
      }
      
      // Process payment (40 lines)
      // ... payment logic
      
      // Send notifications (20 lines)
      // ... notification logic
    }
    
    // ✅ GOOD: Break into smaller methods
    function processOrder(order) {
      validateOrder(order);
      const subtotal = calculateSubtotal(order);
      const discount = applyDiscounts(order, subtotal);
      const total = subtotal - discount;
      processPayment(order, total);
      sendOrderConfirmation(order);
    }
    

    Large Class

    // ❌ BAD: Class doing too much
    class User {
      // User properties
      name;
      email;
      
      // Database methods
      save() {}
      update() {}
      delete() {}
      
      // Authentication methods
      login() {}
      logout() {}
      resetPassword() {}
      
      // Email methods
      sendWelcomeEmail() {}
      sendPasswordResetEmail() {}
      
      // Reporting methods
      generateUserReport() {}
      exportUserData() {}
    }
    
    // ✅ GOOD: Split into focused classes
    class User {
      name;
      email;
    }
    
    class UserRepository {
      save(user) {}
      update(user) {}
      delete(user) {}
    }
    
    class AuthenticationService {
      login(user) {}
      logout(user) {}
      resetPassword(user) {}
    }
    
    class EmailService {
      sendWelcomeEmail(user) {}
      sendPasswordResetEmail(user) {}
    }
    
    class UserReportService {
      generateReport(user) {}
      exportData(user) {}
    }
    

    Long Parameter List

    // ❌ BAD: Too many parameters
    function createUser(
      firstName,
      lastName,
      email,
      phone,
      address,
      city,
      country,
      zipCode,
      dateOfBirth
    ) {
      // ...
    }
    
    // ✅ GOOD: Use parameter object
    function createUser(userDetails) {
      const {
        firstName,
        lastName,
        email,
        phone,
        address,
        city,
        country,
        zipCode,
        dateOfBirth
      } = userDetails;
      // ...
    }
    
    // Or with TypeScript
    interface UserDetails {
      firstName: string;
      lastName: string;
      email: string;
      phone: string;
      address: string;
      city: string;
      country: string;
      zipCode: string;
      dateOfBirth: Date;
    }
    
    function createUser(details: UserDetails) {
      // ...
    }
    

    Object-Orientation Abusers

    Switch Statements

    // ❌ BAD: Switch statement that will grow
    function calculateShippingCost(order) {
      switch (order.shippingMethod) {
        case 'standard':
          return order.weight * 5;
        case 'express':
          return order.weight * 10;
        case 'overnight':
          return order.weight * 20;
        default:
          throw new Error('Unknown shipping method');
      }
    }
    
    // ✅ GOOD: Use polymorphism
    class ShippingMethod {
      calculateCost(order) {
        throw new Error('Must be implemented by subclass');
      }
    }
    
    class StandardShipping extends ShippingMethod {
      calculateCost(order) {
        return order.weight * 5;
      }
    }
    
    class ExpressShipping extends ShippingMethod {
      calculateCost(order) {
        return order.weight * 10;
      }
    }
    
    class OvernightShipping extends ShippingMethod {
      calculateCost(order) {
        return order.weight * 20;
      }
    }
    
    // Or use strategy pattern with objects
    const SHIPPING_STRATEGIES = {
      standard: (order) => order.weight * 5,
      express: (order) => order.weight * 10,
      overnight: (order) => order.weight * 20,
    };
    
    function calculateShippingCost(order) {
      const strategy = SHIPPING_STRATEGIES[order.shippingMethod];
      if (!strategy) throw new Error('Unknown shipping method');
      return strategy(order);
    }
    

    Change Preventers

    Divergent Change

    // ❌ BAD: Class changes for multiple reasons
    class Order {
      // Changes when database schema changes
      saveToDB() {}
      
      // Changes when calculation logic changes
      calculateTotal() {}
      
      // Changes when notification requirements change
      sendEmail() {}
      
      // Changes when reporting format changes
      generateInvoice() {}
    }
    
    // ✅ GOOD: Separate concerns
    class Order {
      items;
      total;
    }
    
    class OrderRepository {
      save(order) {}
    }
    
    class OrderCalculator {
      calculateTotal(order) {}
    }
    
    class OrderNotifier {
      sendEmail(order) {}
    }
    
    class InvoiceGenerator {
      generate(order) {}
    }
    

    Shotgun Surgery

    // ❌ BAD: One change requires editing many classes
    // Change commission rate requires editing:
    // - SalesReport class
    // - PaymentCalculator class
    // - InvoiceGenerator class
    // - DashboardWidget class
    // - EmailTemplate class
    
    // ✅ GOOD: Centralize related logic
    class CommissionConfig {
      static COMMISSION_RATE = 0.10;
      
      static getRate() {
        return this.COMMISSION_RATE;
      }
      
      static calculateCommission(amount) {
        return amount * this.COMMISSION_RATE;
      }
    }
    
    // All classes use CommissionConfig
    class SalesReport {
      generateReport(sales) {
        const commission = CommissionConfig.calculateCommission(sales.total);
      }
    }
    

    Dispensables

    Duplicate Code

    // ❌ BAD: Duplicated validation logic
    function createUser(data) {
      if (!data.email) throw new Error('Email required');
      if (!/\S+@\S+\.\S+/.test(data.email)) throw new Error('Invalid email');
      // ... create user
    }
    
    function updateUser(id, data) {
      if (!data.email) throw new Error('Email required');
      if (!/\S+@\S+\.\S+/.test(data.email)) throw new Error('Invalid email');
      // ... update user
    }
    
    // ✅ GOOD: Extract common logic
    function validateEmail(email) {
      if (!email) throw new Error('Email required');
      if (!/\S+@\S+\.\S+/.test(email)) throw new Error('Invalid email');
    }
    
    function createUser(data) {
      validateEmail(data.email);
      // ... create user
    }
    
    function updateUser(id, data) {
      validateEmail(data.email);
      // ... update user
    }
    

    Dead Code

    // ❌ BAD: Commented out code
    function processOrder(order) {
      validateOrder(order);
      // const discount = calculateDiscount(order);
      // order.total = order.subtotal - discount;
      calculateTotal(order);
      processPayment(order);
    }
    
    // Old function no longer used
    // function calculateDiscount(order) {
    //   return order.subtotal * 0.1;
    // }
    
    // ✅ GOOD: Remove dead code (use git to recover if needed)
    function processOrder(order) {
      validateOrder(order);
      calculateTotal(order);
      processPayment(order);
    }
    

    Speculative Generality

    // ❌ BAD: Overly generic for no reason
    class AbstractBaseUserFactoryProvider {
      createUserFactoryStrategy() {
        return new ConcreteUserFactory();
      }
    }
    
    // ✅ GOOD: Keep it simple until generality is needed
    class UserService {
      createUser(data) {
        return new User(data);
      }
    }
    

    Refactoring Techniques

    Extract Method

    Break long methods into smaller, focused ones:

    // ❌ BEFORE
    function printOwing(invoice) {
      console.log('***********************');
      console.log('**** Customer Owes ****');
      console.log('***********************');
      
      let outstanding = 0;
      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}`);
    }
    

    Inline Method

    Replace unnecessary methods with their content:

    // ❌ BEFORE: Unnecessary indirection
    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

    Make complex expressions readable:

    // ❌ BEFORE
    function price(order) {
      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
    function price(order) {
      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;
    }
    

    Replace Temp with Query

    Replace temporary variables with method calls:

    // ❌ BEFORE
    function getPrice() {
      const basePrice = quantity * itemPrice;
      const discountFactor = basePrice > 1000 ? 0.95 : 0.98;
      return basePrice * discountFactor;
    }
    
    // ✅ AFTER
    function getPrice() {
      return basePrice() * discountFactor();
    }
    
    function basePrice() {
      return quantity * itemPrice;
    }
    
    function discountFactor() {
      return basePrice() > 1000 ? 0.95 : 0.98;
    }
    

    Replace Conditional with Polymorphism

    Use inheritance instead of conditionals:

    // ❌ BEFORE
    class Bird {
      getSpeed() {
        switch (this.type) {
          case 'European':
            return this.baseSpeed;
          case 'African':
            return this.baseSpeed - this.loadFactor;
          case 'Norwegian':
            return this.isNailed ? 0 : this.baseSpeed;
        }
      }
    }
    
    // ✅ AFTER
    class Bird {
      getSpeed() {
        throw new Error('Must be implemented by subclass');
      }
    }
    
    class EuropeanBird extends Bird {
      getSpeed() {
        return this.baseSpeed;
      }
    }
    
    class AfricanBird extends Bird {
      getSpeed() {
        return this.baseSpeed - this.loadFactor;
      }
    }
    
    class NorwegianBird extends Bird {
      getSpeed() {
        return this.isNailed ? 0 : this.baseSpeed;
      }
    }
    

    Introduce Parameter Object

    Group related parameters:

    // ❌ BEFORE
    function amountInvoiced(startDate, endDate) {}
    function amountReceived(startDate, endDate) {}
    function amountOverdue(startDate, endDate) {}
    
    // ✅ AFTER
    class DateRange {
      constructor(startDate, endDate) {
        this.startDate = startDate;
        this.endDate = endDate;
      }
    }
    
    function amountInvoiced(dateRange) {}
    function amountReceived(dateRange) {}
    function amountOverdue(dateRange) {}
    

    Replace Magic Number with Constant

    Give numbers meaningful names:

    // ❌ BEFORE
    function potentialEnergy(mass, height) {
      return mass * height * 9.81;
    }
    
    // ✅ AFTER
    const GRAVITATIONAL_CONSTANT = 9.81;
    
    function potentialEnergy(mass, height) {
      return mass * height * GRAVITATIONAL_CONSTANT;
    }
    

    Refactoring Patterns

    Guard Clauses

    Replace nested conditionals with early returns:

    // ❌ BEFORE
    function getPayAmount(employee) {
      let result;
      if (employee.isSeparated) {
        result = { amount: 0, reasonCode: 'SEP' };
      } else {
        if (employee.isRetired) {
          result = { amount: 0, reasonCode: 'RET' };
        } else {
          // ... calculate normal pay
          result = { amount: normalPay, reasonCode: 'NORM' };
        }
      }
      return result;
    }
    
    // ✅ AFTER
    function getPayAmount(employee) {
      if (employee.isSeparated) {
        return { amount: 0, reasonCode: 'SEP' };
      }
      if (employee.isRetired) {
        return { amount: 0, reasonCode: 'RET' };
      }
      // ... calculate normal pay
      return { amount: normalPay, reasonCode: 'NORM' };
    }
    

    Consolidate Conditional Expression

    Combine related conditions:

    // ❌ BEFORE
    function disabilityAmount(employee) {
      if (employee.seniority < 2) return 0;
      if (employee.monthsDisabled > 12) return 0;
      if (employee.isPartTime) return 0;
      // ... calculate disability
    }
    
    // ✅ AFTER
    function disabilityAmount(employee) {
      if (isNotEligibleForDisability(employee)) return 0;
      // ... calculate disability
    }
    
    function isNotEligibleForDisability(employee) {
      return employee.seniority < 2 
        || employee.monthsDisabled > 12 
        || employee.isPartTime;
    }
    

    Decompose Conditional

    Extract conditions and actions into methods:

    // ❌ BEFORE
    if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
      charge = quantity * winterRate + winterServiceCharge;
    } else {
      charge = quantity * summerRate;
    }
    
    // ✅ AFTER
    if (isWinter(date)) {
      charge = winterCharge(quantity);
    } else {
      charge = summerCharge(quantity);
    }
    
    function isWinter(date) {
      return date.before(SUMMER_START) || date.after(SUMMER_END);
    }
    
    function winterCharge(quantity) {
      return quantity * winterRate + winterServiceCharge;
    }
    
    function summerCharge(quantity) {
      return quantity * summerRate;
    }
    

    Safe Refactoring Process

    Step-by-Step Approach

  • Run tests - Ensure all tests pass
  • Make small change - One refactoring at a time
  • Run tests again - Verify nothing broke
  • Commit - Save working state
  • Repeat - Continue with next refactoring
  • Red-Green-Refactor (TDD)

    // 1. RED: Write failing test
    test('calculates discount for bulk orders', () => {
      const order = { quantity: 100, itemPrice: 10 };
      expect(calculateDiscount(order)).toBe(50);
    });
    
    // 2. GREEN: Make it pass (quick and dirty)
    function calculateDiscount(order) {
      if (order.quantity > 50) {
        return order.quantity * order.itemPrice * 0.05;
      }
      return 0;
    }
    
    // 3. REFACTOR: Clean up code
    function calculateDiscount(order) {
      const BULK_THRESHOLD = 50;
      const BULK_DISCOUNT_RATE = 0.05;
      
      if (!isBulkOrder(order)) return 0;
      
      return basePrice(order) * BULK_DISCOUNT_RATE;
    }
    
    function isBulkOrder(order) {
      return order.quantity > BULK_THRESHOLD;
    }
    
    function basePrice(order) {
      return order.quantity * order.itemPrice;
    }
    

    Tools for Refactoring

    IDE Support

  • Visual Studio Code: Rename Symbol, Extract Method, Move to File
  • WebStorm: Comprehensive refactoring tools
  • IntelliJ IDEA: Advanced refactoring capabilities
  • Automated Tools

  • ESLint: Catch code quality issues
  • SonarQube: Detect code smells
  • Prettier: Auto-format code
  • JSCodeshift: Automated code transformations
  • Tips

  • Refactor in small steps - One change at a time
  • Run tests frequently - After each small change
  • Commit often - Save working states
  • Don't refactor while adding features - Separate concerns
  • Use IDE refactoring tools - Safer than manual changes
  • Have good test coverage - Catch regressions early
  • Get code reviews - Fresh eyes catch issues
  • Refactor continuously - Don't let debt accumulate
  • Learn to recognize smells - Practice identifying problems
  • Know when to stop - Perfect is the enemy of good
  • Common Mistakes

  • Refactoring without tests - Risky without safety net
  • Making multiple changes at once - Hard to isolate issues
  • Changing behavior while refactoring - Keep behavior unchanged
  • Over-engineering - Don't add complexity unnecessarily
  • Refactoring legacy code without understanding it first - Learn before changing
  • Resources

  • Refactoring by Martin Fowler
  • Refactoring Guru
  • SourceMaking - Refactoring
  • Clean Code by Robert C. Martin
  • Working Effectively with Legacy Code by Michael Feathers
  • Topics

    RefactoringCode QualitySoftware EngineeringBest Practices

    Found This Helpful?

    If you have questions or suggestions for improving these notes, I'd love to hear from you.