Write better Javascript, EP 1: Conditionals

Reading Time: 5 minutes

Write better Javascript, EP 1: Conditionals

This is the first "episode" of one of many series I intend to write occasionally if my personal time allows me to. This first one tries to encourage developers to write more readable code on their applications, also it's focused on the Javascript language, but in some cases, you may find some tips useful for another language; creativity doesn't have any limits.

Let's get this party started then!

One of the most used control structures we see every day while we're coding those amazing, efficient and modern applications are the if ... else block, or more formally called conditionals, those evaluate an expression or a value and execute different code depending on if that evaluation is either true or false.

But well, you might have already known this, so let's begin with some useful facts, techniques, and ways to write better conditionals.

1. There's no else if statement

It's pretty common to find code like this:

function greet(message) {
  if (message === 'Hello!') {
    console.log('Hello there!');
  } else if (message === 'Wazaaaa!') {
    console.log('Waaaazaaaaaaa!!!');
  }
}

Actually, it's the same as writing this:

function greet(message) {
  if (message === 'Hello!') {
    console.log('Hello there!');
  } else {
    if (message === 'Wazaaaa!') {
      console.log('Waaaazaaaaaaa!!!');
    }
  }
}

Do you know that you can write conditionals without curly braces?

if (foo) bar()

You can do the same thing with the else part of the first if statement when you write else if, you're actually skipping the curly braces for the second if statement, the one provided to else.

2. Using more return means saying goodbye to else

Look at the next code:

function rateScore(score) {
  if (score < 5) {
    return 'poor';
  } else if (score >= 5 && score < 9) {
    return 'pass';
  } else if (score >= 9) {
    return 'outstanding';
  }
}

If the score is less than 5, the function will return 'poor' and the rest of the function will be skipped so we can rewrite this as:

function rateScore(score) {
  if (score < 5) {
    return 'poor';
  }
  if (score >= 5) {
    return 'pass';
  }
  if (score >= 9) {
    return 'outstanding';
  }
}

One more catch: if one of the first conditions aren't true, the last one will always return 'outstanding', so we can remove the last condition:

function rateScore(score) {
  if (score < 5) {
    return 'poor';
  }
  if (score >= 5) {
    return 'pass';
  }
  return 'outstanding';
}

3. Maps and Object Literals over the switch statement

Look at the next example, we get some awesome animal breeds:

function getAwesomeBreed(animal) {
  switch (animal) {
    case 'dog':
      return ['border collie', 'dalmatian', 'pug', 'chihuahua'];
    case 'cat':
      return ['persian', 'russian blue', 'siamese', 'ragdoll'];
    case 'bird':
      return ['cockatiel', 'cockatoo', 'parrot', 'chicken'];
    default:
      return [];
  }
}

Too verbose, isn't it? Let's clean it up a little bit:

function getAwesomeBreed(animal) {
  // Using object literal
  const breeds = {
    dog: ['border collie', 'dalmatian', 'pug', 'chihuahua'],
    cat: ['persian', 'russian blue', 'siamese', 'ragdoll'],
    bird: ['cockatiel', 'cockatoo', 'parrot', 'chicken']
  };

  return breeds[animal] || [];
}

Not convinced yet? Let's do something else:

function getAwesomeBreed(animal) {
  // Using Map, available from ES2015
  const breed = new Map()
    .set(dog, ['border collie', 'dalmatian', 'pug', 'chihuahua'])
    .set(cat, ['persian', 'russian blue', 'siamese', 'ragdoll'])
    .set(bird, ['cockatiel', 'cockatoo', 'parrot', 'chicken']);

  return breed.get(animal);
}

Use whatever syntax/method suits your needs, personally, I would go for object literals whenever is possible.

4. Using Array methods

We can reduce a significant amount of code if we use the functions included out of the box of some objects, let's check if all the animals are dogs below:

function checkAllDogs() {
  const animals = [
    { type: 'dog', breed: 'border-collie' },
    { type: 'cat', breed: 'persian' },
    { type: 'dog', breed: 'german shepherd' }
  ];

  let areAllDogs = true;

  for (let animal of animals) {
    if (!areAllDogs) break;
    areAllDogs = (animal.type === 'dog');
  }

  console.log(areAllDogs);
}

What if we use some Array methods?

function checkAllDogs() {
  const animals = [
    { type: 'dog', breed: 'border-collie' },
    { type: 'cat', breed: 'persian' },
    { type: 'dog', breed: 'german shepherd' }
  ];

  const areAllDogs = animals.every(animal => animal.type === 'dog');

  console.log(areAllDogs);
}

Better? What if we want to know if there's something different than a dog?

function checkAllDogs() {
  const animals = [
    { type: 'dog', breed: 'border-collie' },
    { type: 'cat', breed: 'persian' },
    { type: 'dog', breed: 'german shepherd' }
  ];

  const areAllDogs = animals.some(animal => animal.type !== 'dog');

  console.log(!areAllDogs);
}

5. Using default function parameters and destructuring.

At least once in a while, you need to check for null and undefined values inside your functions, we can do it in a fancy way now:

function test(animal, quantity) {
  if (!animal) return;
  const realQuantity = quantity || 1;

  console.log(`There are ${realQuantity} ${animal}`);
}

We can get rid of the realQuantity variable:

function test(animal, quantity = 1) { // Default value if nothing is provided
  if (!animal) return;

  console.log(`There are ${quantity} ${animal}`);
}

What if one of the parameters is an object, is it possible to give a default parameter too?

function test(animal) {
  if (animal &amp;&amp; animal.breed) {
    console.log(animal.breed);
  } else {
    console.log('unknown');
  }
}

We can avoid checking for animal & animal.breed with default parameters and destructing.

// Destructing will allow us to get the breed property only
function test({name} = {}) {
  console.log(name || 'unknown');
}

We assign an empty object by default because otherwise, we will get some error like Cannot destructure property name of 'undefined' or 'null', and it's right, there could be the case where there's no name property in undefined.

5. Using Array.includes when there are multiple criteria.

Let's take a look:

function test(animal) {
  if (animal === 'dog' || animal === 'cat') {
    console.log('pet');
  }
}

This may look fine for now, but what if there are more conditions, like 'bird', 'spider'? There's an elegant way to do it:

function test(animal) {
  const pets = ['dog', 'cat', 'bird', 'spider'];

  if (pets.includes(animal)) {
    console.log('red');
  }
}

Better, isn't it?

6. Return early

Look at this last example:

function test(animal, quantity) {
  const pets = ['dog', 'cat', 'bird', 'spider'];

  // Fist condition: animal should have a value
  if (animal)  {
    // Second condition: must be a pet

    if (pets.includes(animal)) {
      console.log('pet');

      // Third condition
      if (quantity > 5) {
        console.log('animaaaaaals!');
      }
    }
  } else {
    throw('No animal! :(');
  }
}

Now let's try again doing early return:

function test(animal, quantity) {
  const pets = ['dog', 'cat', 'bird', 'spider'];

  // Return error early
  if (!animal) throw('No animal! :(');

  // Must be a pet
  if(pets.includes(animal)) {
    console.log('pet');

    // Big quantity
    if (quantity > 5) {
      console.log('animaaaals!');
    }
  }
}

Is that all? We can do it better! Let's invert conditions and return early.

function test(animal, quantity) {
  const pets = ['dog', 'cat', 'bird', 'spider'];

  if (!animal) throw('No animal! :(');  // Check presence
  if (!pets.includes(animal)) return; // Must be a pet

  console.log('pet');

  if (quantity > 5) {
    console.log('animaaaals!');
  }
}

We find this useful when we have long logic and avoiding further execution is important.

Summary

We usually try to code the first thing that comes out of our minds, but we should be aware that maybe in the future there’s gonna be someone else looking at our code and trying to refactor or perhaps just understand what you were thinking at the time you wrote that code, so, we should make it easier for everybody to understand the purpose of our code and maybe for a future version, who knows? Refactor could come in handy in the future.

This is the first step of many to write better code and get the advantage of this amazing programming language, try to write a few lines of code and apply these tricks to make it more readable.

Know more about the great place where I work, here!

0 Shares:
You May Also Like
administrate gem
Read More

Administrate review

Reading Time: 6 minutes Recently I’ve been playing with Administrate a gem from Thoughtbot, for a new e-commerce project built from scratch.…