Sling Academy
Home/JavaScript/Refactoring Lengthy Conditionals into More Manageable Segments in JavaScript

Refactoring Lengthy Conditionals into More Manageable Segments in JavaScript

Last updated: December 12, 2024

In software development, dealing with complex and lengthy conditionals can often lead to code that's difficult to read, understand, and maintain. These large if-else chains or nested conditionals, commonly known as "spaghetti code," can increase the risk of bugs and make enhancements more time-consuming. Fortunately, in JavaScript, we have several techniques to refactor such conditionals into more manageable and cleaner structures. Let's delve into some of these methods using code examples to demonstrate their implementation.

Problem Statement

Imagine a function that determines the shipping cost based on a customer object. Initially, the implementation uses a long if-else chain to set different shipping costs under certain conditions:

function calculateShippingCost(customer) {
  if (customer.membership === 'premium') {
    if (customer.totalOrders > 10) {
      return 0;
    } else {
      return 5;
    }
  } else if (customer.membership === 'standard') {
    if (customer.totalOrders > 20) {
      return 10;
    } else {
      return 15;
    }
  } else {
    return 25;
  }
}

This code works, but it becomes more challenging to read and maintain as new conditions are added. Let's explore approaches to refactor this.

Using Helper Functions

One way to refactor complex conditionals is to break them down into simpler, smaller functions. Helper functions improve code readability and reusability:

function isPremiumMember(customer) {
  return customer.membership === 'premium';
}

function hasManyOrders(customer) {
  return customer.totalOrders > 10;
}

function calculateShippingCostRefactored(customer) {
  if (isPremiumMember(customer)) {
    return hasManyOrders(customer) ? 0 : 5;
  } else if (customer.membership === 'standard') {
    return customer.totalOrders > 20 ? 10 : 15;
  }
  return 25;
}

This approach abstracts the condition logic into meaningful names, significantly enhancing clarity.

Utilizing a Configuration Object

Another technique is to use an object to map conditions to shipping costs. This strategy uses object lookups, which are more scalable for numerous conditions:

const shippingCosts = {
  premium: { threshold: 10, low: 0, high: 5 },
  standard: { threshold: 20, low: 10, high: 15 },
  basic: { low: 25 }
};

function getShippingCost(customer) {
  const config = shippingCosts[customer.membership] || shippingCosts.basic;
  if (config.threshold && customer.totalOrders > config.threshold) {
    return config.low;
  }
  return config.high || config.low;
}

This pattern allows easy adjustments to thresholds and return values without altering the logic in the function, making it an ideal choice for larger or changing condition sets.

Adopting a Strategy Pattern

For even more complicated scenarios, consider applying the strategy pattern. It involves encapsulating algorithms into classes or functions, allowing for the swapping of implementation at runtime:

class PremiumShipping {
  isEligible(customer) {
    return customer.membership === 'premium';
  }
  calculate(customer) {
    return customer.totalOrders > 10 ? 0 : 5;
  }
}

class StandardShipping {
  isEligible(customer) {
    return customer.membership === 'standard';
  }
  calculate(customer) {
    return customer.totalOrders > 20 ? 10 : 15;
  }
}

class BasicShipping {
  isEligible() {
    return true;
  }
  calculate() {
    return 25;
  }
}

function calculateShippingStrategy(customer) {
  const strategies = [new PremiumShipping(), new StandardShipping(), new BasicShipping()];
  const strategy = strategies.find(s => s.isEligible(customer));
  return strategy.calculate(customer);
}

Using the strategy pattern separates different paths into distinct strategies, each adhering to an interface. This results in highly organized and adaptable code.

Conclusion

Refactoring lengthy and complex conditionals into more manageable pieces not only improves readability but also maintains, enhances, and debugs flexibility. JavaScript developers can utilize helper functions, configuration objects, or design patterns like the strategy pattern, each offering specific benefits. By incorporating these techniques, you streamline your code, minimize errors, and efficiently manage growth in applications.

Next Article: Minimizing Nested Conditionals by Early Returns in JavaScript Functions

Previous Article: Simplifying Boolean Expressions to Enhance Readability in JavaScript

Series: Mastering Control Flow in JavaScript

JavaScript

You May Also Like

  • Handle Zoom and Scroll with the Visual Viewport API in JavaScript
  • Improve Security Posture Using JavaScript Trusted Types
  • Allow Seamless Device Switching Using JavaScript Remote Playback
  • Update Content Proactively with the JavaScript Push API
  • Simplify Tooltip and Dropdown Creation via JavaScript Popover API
  • Improve User Experience Through Performance Metrics in JavaScript
  • Coordinate Workers Using Channel Messaging in JavaScript
  • Exchange Data Between Iframes Using Channel Messaging in JavaScript
  • Manipulating Time Zones in JavaScript Without Libraries
  • Solving Simple Algebraic Equations Using JavaScript Math Functions
  • Emulating Traditional OOP Constructs with JavaScript Classes
  • Smoothing Out User Flows: Focus Management Techniques in JavaScript
  • Creating Dynamic Timers and Counters with JavaScript
  • Implement Old-School Data Fetching Using JavaScript XMLHttpRequest
  • Load Dynamic Content Without Reloading via XMLHttpRequest in JavaScript
  • Manage Error Handling and Timeouts Using XMLHttpRequest in JavaScript
  • Handle XML and JSON Responses via JavaScript XMLHttpRequest
  • Make AJAX Requests with XMLHttpRequest in JavaScript
  • Customize Subtitle Styling Using JavaScript WebVTT Integration