Monday, October 7

5 Tips to Write Better Conditionals in JavaScript

When working with JavaScript, we deal a lot with conditionals, here are the 5 tips for you to write better / cleaner conditionals.

1. Use Array.includes for Multiple Criteria

Let’s take a look at the example below:

// condition
function test(fruit) {
  if (fruit == 'apple' || fruit == 'strawberry') {
    console.log('red');
  }
}

At first glance, the above example looks good. However, what if we get more red fruits, say cherry and cranberries? Are we going to extend the statement with more || ?

We can rewrite the conditional above by using Array.includes (Array.includes)

function test(fruit) {
  // extract conditions to array
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  if (redFruits.includes(fruit)) {
    console.log('red');
  }
}

We extract the red fruits (conditions) to an array. By doing this, the code looks tidier.

2. Less Nesting, Return Early

Let’s expand the previous example to include two more conditions:

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // condition 1: fruit must has value
  if (fruit) {
    // condition 2: must be red
    if (redFruits.includes(fruit)) {
      console.log('red');

      // condition 3: must be big quantity
      if (quantity > 10) {
        console.log('big quantity');
      }
    }
  } else {
    throw('No fruit!');
  }
}

// test results
test(null); // error: No fruits
test('apple'); // print: red
test('apple', 20); // print: red, big quantity

Look at the code above, we have:

A general rule I personally follow is return early when invalid conditions found.

/_ return early when invalid conditions found _/

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // condition 1: throw error early
  if (!fruit) throw('No fruit!');

  // condition 2: must be red
  if (redFruits.includes(fruit)) {
    console.log('red');

    // condition 3: must be big quantity
    if (quantity > 10) {
      console.log('big quantity');
    }
  }
}

By doing this, we have one less level of nested statement. This coding style is good especially when you have long if statement (imagine you need to scroll to the very bottom to know there is an else statement, not cool).

We can further reduce the nesting if, by inverting the conditions & return early. Look at condition 2 below to see how we do it:

/_ return early when invalid conditions found _/

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  if (!fruit) throw('No fruit!'); // condition 1: throw error early
  if (!redFruits.includes(fruit)) return; // condition 2: stop when fruit is not red

  console.log('red');

  // condition 3: must be big quantity
  if (quantity > 10) {
    console.log('big quantity');
  }
}

By inverting the conditions of condition 2, our code is now free of a nested statement. This technique is useful when we have long logic to go and we want to stop further process when a condition is not fulfilled.

However, that’s no hard rule for doing this. Ask yourself, is this version (without nesting) better / more readable than the previous one (condition 2 with nested)?

For me, I would just leave it as the previous version (condition 2 with nested) because:

Therefore, always aim for Less Nesting and Return Early but don’t overdo it. There is an article & StackOverflow discussion that talks further on this topic if you interested:

3. Use Default Function Parameters and Destructuring

I guess the code below might look familiar to you, we always need to check for null / undefined value and assign default value when working with JavaScript:

function test(fruit, quantity) {
  if (!fruit) return;
  const q = quantity || 1; // if quantity not provided, default to one

  console.log(`We have ${q} ${fruit}!`);
}

//test results
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!

In fact, we can eliminate the variable q by assigning default function parameters.

function test(fruit, quantity = 1) { // if quantity not provided, default to one
  if (!fruit) return;
  console.log(`We have ${quantity} ${fruit}!`);
}

//test results
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!

Much easier & intuitive isn’t it? Please note that each parameter can has it own default function parameter. For example, we can assign default value to fruit too: function test(fruit = 'unknown', quantity = 1).

What if our fruit is an object? Can we assign default parameter?

function test(fruit) { 
  // printing fruit name if value provided
  if (fruit && fruit.name)  {
    console.log (fruit.name);
  } else {
    console.log('unknown');
  }
}

//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

Look at the example above, we want to print the fruit name if it’s available or we will print unknown. We can avoid the conditional fruit && fruit.name checking with default function parameter & destructing.

// destructing - get name property only
// assign default empty object {}
function test({name} = {}) {
  console.log (name || 'unknown');
}

//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

Since we only need property name from fruit, we can destructure the parameter using {name}, then we can use name as variable in our code instead of fruit.name.

We also assign empty object {} as default value. If we do not do so, you will get error when executing the line test(undefined)Cannot destructure property name of 'undefined' or 'null'. because there is no name property in undefined.

If you don’t mind using 3rd party libraries, there are a few ways to cut down null checking:

Here is an example of using Lodash:

// Include lodash library, you will get _
function test(fruit) {
  console.log(_.get(fruit, 'name', 'unknown'); // get property name, if not available, assign default value 'unknown'
}

//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

You may run the demo code here. Besides, if you are a fan of Functional Programming (FP), you may opt to use Lodash fp, the functional version of Lodash (method changed to get or getOr).

4. Favor Map / Object Literal than Switch Statement

Let’s look at the example below, we want to print fruits based on color:

function test(color) {
  // use switch case to find fruits in color
  switch (color) {
    case 'red':
      return ['apple', 'strawberry'];
    case 'yellow':
      return ['banana', 'pineapple'];
    case 'purple':
      return ['grape', 'plum'];
    default:
      return [];
  }
}

//test results
test(null); // []
test('yellow'); // ['banana', 'pineapple']

The above code seems nothing wrong, but I find it quite verbose. The same result can be achieve with object literal with cleaner syntax:

function test(color) {
  // use object literal to find fruits in color
  const fruitColor = {
    red: ['apple', 'strawberry'],
    yellow: ['banana', 'pineapple'],
    purple: ['grape', 'plum']
  };

  return fruitColor[color] || [];
}

Alternatively, you may use Map to achieve the same result:

function test(color) {
  // use Map to find fruits in color
  const fruitColor = new Map()
    .set('red', ['apple', 'strawberry'])
    .set('yellow', ['banana', 'pineapple'])
    .set('purple', ['grape', 'plum']);

  return fruitColor.get(color) || [];
}

Map is the object type available since ES2015, allow you to store key value pair.

Should we ban the usage of switch statement? Do not limit yourself to that. Personally, I use object literal whenever possible, but I wouldn’t set hard rule to block that, use whichever make sense for your scenario.

Todd Motto has an article that dig deeper on switch statement vs object literal, you may read here.

TL;DR; Refactor the syntax

For the example above, we can actually refactor our code to achieve the same result with Array.filter .

function test(color) {
  // use Array filter to find fruits in color
  const fruits = [
    { name: 'apple', color: 'red' }, 
    { name: 'strawberry', color: 'red' }, 
    { name: 'banana', color: 'yellow' }, 
    { name: 'pineapple', color: 'yellow' }, 
    { name: 'grape', color: 'purple' }, 
    { name: 'plum', color: 'purple' }   ];

  return fruits.filter(f => f.color == color);
}

There’s always more than 1 way to achieve the same result. We have shown 4 with the same example. Coding is fun!

5. Use Array.every & Array.some for All / Partial Criteria

This last tip is more about utilizing new (but not so new) Javascript Array function to reduce the lines of code. Look at the code below, we want to check if all fruits are in red color:

function test(fruits) {
  const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

  let isAllRed = true;

  // condition: all fruits must be red
  for (let f of fruits) {
    if (!isAllRed) break;
    isAllRed = (f.color == 'red');
  }

  console.log(isAllRed); // false
}

The code is so long! We can reduce the number of lines with Array.every:

function test(fruits) {
  const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

  // condition: short way, all fruits must be red
  const isAllRed = fruits.every(f => f.color == 'red');

  console.log(isAllRed); // false
}

Much cleaner now right? In a similar way, if we want to test if any of the fruit is red, we can use Array.some to achieve it in one line.

function test(fruits) {
  const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

  // condition: if any fruit is red
  const isAnyRed = fruits.some(f => f.color == 'red');

  console.log(isAnyRed); // true
}

Summary

Let’s produce more readable code together. I hope you learn something new in this article.

That’s all. Happy coding!


Source: Scotch.io

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x