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 && 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!