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.