There's Such a Thing As Using Too Many Ifs
How Many Is Too Many?
Some people think that number is one and you should always substitute at least a ternary for any single if
statements. I don't take that staunch of an approach, but I want to highlight some ways to escape common if/else
spaghetti code.
I believe a lot of developers fall into the if/else
trap so easily, not because of the complexity of other solutions, but because it follows such a natural language pattern:
if
something do this, else
do this instead.
Wait, What's a Ternary?
I already know, let's skip this.A ternary isn't a revolutionary difference from an if/else
as they're both conditional operations, but a ternary does return a value so it can be used directly in an assignment.
const greaterThanFive = (8 > 5) ? 'yep' : 'nope';
console.log(greaterThanFive); // 'yep'
The basic pattern is just a condition, one value to return if truthy, and one value to return if falsy.
(condition) ? isTruthy : isFalsy
An alternative to If/Else
Let's start with a scenario and walk through examples of different solutions.
We'll be taking colors from user input and need to turn them into some preset color codes to match so we can change our background color. So we'll check for strings of color names and set our color code if we have a match.
const setBackgroundColor = (colorName) => {
let colorCode = '';
if(colorName === 'blue') {
colorCode = '#2196F3';
} else if(colorName === 'green') {
colorCode = '#4CAF50';
} else if(colorName === 'orange') {
colorCode = '#FF9800';
} else if(colorName === 'pink') {
colorCode = '#E91E63';
} else {
colorCode = '#F44336';
};
document.body.style.backgroundColor = colorCode;
};
This if/else
gets the job done. But we're saddled with a lot of repetitive logic comparing colorName
and repetitive assignment of colorCode
.
Switch
Now we could more appropriately change this into a switch
statement. It better fits the concept of what we're trying to do; We have several cases of strings we want to match, and a default if none of our cases match.
const setBackgroundColor = (colorName) => {
let colorCode = '';
switch(colorName) {
case 'blue':
colorCode = '#2196F3';
break;
case 'green':
colorCode = '#4CAF50';
break;
case 'orange':
colorCode = '#FF9800';
break;
case 'pink':
colorCode = '#E91E63';
break;
default:
colorCode = '#f44336';
};
document.body.style.backgroundColor = colorCode;
};
But a switch
still comes with a lot of boilerplate and repetitive code we could do without.
Lookup Table
So what are we really trying to accomplish here? We need to assign color codes in hex to color names, so let's create an object that holds color names as keys, and color codes as values. Then we can look up our color code by its color name by using object[key]
. And we need a default value, so a short ternary that returns the default if no key is found will do so all while making the default part of our object.
const colorCodes = {
'blue' : '#2196F3',
'green' : '#4CAF50',
'orange' : '#FF9800',
'pink' : '#E91E63',
'default': '#F44336'
};
const setBackgroundColor = (colorName) => {
document.body.style.backgroundColor = colorCodes[colorName]
? colorCodes[colorName]
: colorCodes['default'];
};
Now we have a lookup table that neatly lays out our inputs and possible outputs.
This isn't about a miraculous 'lines of code' (LOC) reduction (we went from 15 to 20 to 12.) In fact, some of these solutions may increase your LOC, but we've increased maintainability, legibility, and actually reduced complexity by only having one logic check for a default fallback.
Trade Logic For Data
The most important accomplishment of using a lookup table over an if/else
or switch
is that we've turned multiple instances of comparative logic into data. The code is more expressive; it shows the logic as an operation. The code is more testable; the logic has been reduced. And our comparisons are more maintainable; they're consolidated as pure data.
Let's reduce five comparative logic operations to one and transform our values into data.
Scenario: we need to convert grade percentages into their letter grade equivalent.
An if/else
is simple enough; we check from top down if the grade is higher or equal than what's needed to match the letter grade.
const getLetterGrade = (gradeAsPercent) => {
if(gradeAsPercent >= 90) {
return "A";
} else if(gradeAsPercent >= 80) {
return "B";
} else if(gradeAsPercent >= 70) {
return "C";
} else if(gradeAsPercent >= 60) {
return "D";
} else {
return "F"
};
};
But we're repeating the same logic operation over and over.
So let's extract our data into an array (to retain order) and represent each grade possibility as an object. Now we only have to do one >=
comparison on our objects and find the first in our array that matches.
const gradeChart = [
{minpercent: 90, letter: 'A'},
{minpercent: 80, letter: 'B'},
{minpercent: 70, letter: 'C'},
{minpercent: 60, letter: 'D'},
{minpercent: 0, letter: 'F'}
];
const getLetterGrade = (gradeAsPercent) => {
const grade = gradeChart.find(
(grade) => gradeAsPercent >= grade.minpercent
);
return grade.letter;
};
Start Imagining Your Comparisons As Data
When you need to compare or "check" values it's natural to reach for the if/else
so you can verbally step through the problem. But next time try to think of how your values can be represented as data and your logic reduced to interpret that data.
Your code will end up being more readable, maintainable, and purposeful in its intent with a clear separation of the concepts it represents. All of the code examples here function, but the right approach can turn It Works™ Code into 'great to work with' code.