Refactoring cascading conditionals in favor of readability

mangelosanto

Matt Angelosanto

Posted on June 23, 2021

Refactoring cascading conditionals in favor of readability

Written by Juan Cruz Martinez ✏️

JavaScript is an extremely flexible programming language used to build pretty much anything you can think of, from websites, web applications, and desktop applications, to UIs for smart devices, server-side applications, and more.

JavaScript’s flexibility is what has allowed for its broad feature set — but, as we know, it is also responsible for some strange behaviors that have ignited developers' imaginations. Some of the code we’ve written is uniquely suited to solving these strange problems in smart and elegant ways; some isn’t.

In this article, we’ll focus on analyzing the different ways developers have replaced wordy and confusing conditional statements — in particular, cascading if/else if and switch statements. Why? Because in JS, we can do better than just using if.

Ternary, &&, and || operators

Let’s introduce a simple function with a conditional statement using if, and let’s refactor it using the ternary operator.

if (condition) {
   return functionTrue();
} else {
   return functionFalse();
}
Enter fullscreen mode Exit fullscreen mode

There’s nothing wrong with our example above, but we do unnecessarily take up a few lines of code repeating the keyword return. The ternary operator allows for simplification:

return condition ? functionTrue() : functionFalse();
Enter fullscreen mode Exit fullscreen mode

Isn’t that much simpler? But how does it work?

The ternary operator is the only JavaScript operator that takes three operands: a condition followed by a question mark (?), an expression for a truthy conditional followed by a colon (:), and finally an expression for a falsy conditional. Here is what it looks like:

condition ? expressionIfTrue : expressionIfFalse
Enter fullscreen mode Exit fullscreen mode

Note that both true and false expressions must be provided for the ternary operator to work. But what if we only need to do something when the condition is truthy?

JavaScript offers alternative methods of simplifying expressions by making use of the operators && and ||.

Let’s look at a different example where we only need to execute a statement when the condition is satisfied.

if (condition) {
   console.log("it's true!");
}
Enter fullscreen mode Exit fullscreen mode

We could rewrite this statement into a one-liner by using &&, like so:

condition && console.log("it's true!");
Enter fullscreen mode Exit fullscreen mode

The key reason this works is that JavaScript reads the operands in conditional statements from left to right and exits the moment it can invalidate the arguments. So, in the case of &&, if the first statement is falsy, there’s no point in evaluating the next, as the whole expression is falsy.

Similarly, the || operator will continue evaluating the operands until one of them is true, or the whole expression evaluates to false. Take a look at the below example:

trueCondition || console.log("Hello world!"); // does not execute the console.log
falseCondition || console.log("Hello world!"); // executes the console.log
Enter fullscreen mode Exit fullscreen mode

Evaluating multiple results for an expression

Often, when we’re reading or writing code, we find multiple nested if conditions — such as in the following function, which takes the name of a fruit and returns its color.

function getColor(fruit) {
   if (fruit.toLowerCase() === 'apple') {
       return 'red';
   } else if (fruit.toLowerCase() === 'banana') {
       return 'yellow';
   } if (fruit.toLowerCase() === 'orange') {
       return 'orange';
   } if (fruit.toLowerCase() === 'blueberry') {
       return 'blue';
   } if (fruit.toLowerCase() === 'lime') {
       return 'green';
   }

   return 'unknown';
}
Enter fullscreen mode Exit fullscreen mode

Even when the code performs its function as expected, there are several things we could do better. For starters, the method toLowerCase is being called multiple times for each fruit, which could not only affect performance but also make the whole function less readable.

The next optimization would be to avoid repeating the conditionals, which reduces the number of instances we could introduce errors, such as forgetting to include the toLowerCase method in one of our lines.

We can quickly fix this by calling the method only once at the beginning of the function and evaluating each result — but we can do even better by using a switch statement.

function getColor(fruit) {
   switch(fruit.toLowerCase()) {
       case 'apple':
           return 'red';
       case 'banana':
           return 'yellow';
       case 'orange':
           return 'orange';
       case 'blueberry':
           return 'blue';
       case 'lime':
           return 'green';
       default:
           return 'unknown';
   }
}
Enter fullscreen mode Exit fullscreen mode

This is looking much better, but it still doesn’t feel right. There are a lot of repeated keywords, which make it confusing to read.

Below is a different approach — a smarter, more elegant approach like we discussed at the beginning of this article.

function getColor(fruit) {
   const fruits = {
       'apple': 'red',
       'banana': 'yellow',
       'orange': 'orange',
       'blueberry': 'blue',
       'lime': 'green',
   };

   return fruits[fruit.toLowerCase()] || 'unknown';
}
Enter fullscreen mode Exit fullscreen mode

Simply beautiful. It’s easy to identify which fruit corresponds with each color, we aren’t repeating keywords, and it’s clearly read and understood.

This method for solving cascading if statements is called Jump Table. It can work for much more than simple texts or constants; let’s see a more complex example.

Building Map objects

The Jump Table approach is great for simple texts and constants, but how would it work in more complex situations, like when if statements have multiple lines of codes with function calls?

Now that we understand how to simplify statements, the approach for these more complex scenarios is straightforward — it’s all about how we build our Map object.

Let’s build a calculate function with two numbers and an operation as an argument, and return the operation’s result over the two numbers.

function calculate(number1, number2, operation) {
   const operations = {
       '+': (a, b) => a + b,
       '-': (a, b) => a - b,
       '*': (a, b) => a * b,
       '/': (a, b) => a / b,
   }

   return operations[operation]?.(number1, number2) ?? 'invalid operation';
}
Enter fullscreen mode Exit fullscreen mode

As expected, the code looks very clean and a function is clearly assigned to each operation to perform the necessary calculations to get our desired result.

What looks a bit different, and perhaps strange, is the return statement; but the idea behind it is simple, so let’s break it down.

operations[operation]?.(number1, number2)
Enter fullscreen mode Exit fullscreen mode

The first part of the expression will simply return the given operation from the dictionary and execute the function if the key is present. If the key does not exist, it will return undefined. This last part is thanks to the optional chaining operator.

The second part uses the nullish coalescing operator, which returns its right-hand side operand when its left-hand side operand is null or undefined and otherwise returns its left-hand side operand.

?? 'invalid operation';
Enter fullscreen mode Exit fullscreen mode

So, in our case, it will return the result of the operation when the operation is present in the dictionary, or it will return an invalid operation.

Conclusion

JavaScript is a flexible language that offers multiple ways to solve a problem. In this article, we learned various alternatives to traditional if statements that can help you write clearer, better code.

It’s important to have multiple options in your code arsenal because there is no single solution that will be right for every situation. Additionally, JavaScript is evolving, and new ways will be introduced or discovered as new versions roll out, so it’s helpful to stay connected and read the latest articles to stay up to date.

Thanks for reading!


LogRocket: Debug JavaScript errors easier by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket Dashboard Free Trial Banner

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Try it for free.

💖 💪 🙅 🚩
mangelosanto
Matt Angelosanto

Posted on June 23, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related