Categories
JavaScript Best Practices

JavaScript Best Practices — Conditionals

JavaScript is a very forgiving language. It’s easy to write code that runs but has issues in it.

In this article, we’ll look at how we should write conditional statements in our JavaScript programs.

Write the Normal Path Through the Code First

We should take into account the normal paths first, then write the unusual cases.

So we should check the cases that are encounter more frequently than the rare edge cases.

Make Sure that we Branch Correctly on Equality

>= is different from > and also <= is different from < .

Therefore, we should make sure to avoid off-by-one errors by using the right one.

Put the Normal Case After the if Rather than After the else

The normal case is better after the if because it lets us run less code than if we put it in the else .

Short circuit evaluation means that we just run the if statement rather than checking the if statement and then going to the else .

If most cases are successes, then we should put the success case after the if rather than putting it after the else .

For instance, instead of writing:

if (status === 'error') {
  //...
} else if (status === 'success') {
  //...
} else {
  //...
}

We flip the cases around by writing:

if (status === 'success') {
  //...
} else if (status === 'error') {
  //...
} else {
  //...
}

With the error case on top, we can skip all the error cases that we’ve to check for if the status is 'success' .

We also know that the if case is always the normal successful case if we follow this convention.

Follow the if Clause with a Meaningful Statement

We shouldn’t have an if block that does nothing. So instead of writing:

if (condition) {

} else {
  // ...
}

We should instead write:

if (!condition) {
  //...
}

Consider the else Clause

We can use the else clause so that we take into conditions that aren’t taken into account for in the if block.

For instance, we may want to write:

if (MIN <= value && value <= MAX) {
  // do something ...
} else {
  // invalid value, do nothing
}

We may want to have the else block explains why we do nothing if the condition in the if isn’t met.

This way, we make sure that we didn’t ignore the case opposite of the condition in the if block.

Test the else Clause for Correctness

We want to test all cases for correctness in our program.

So we should test the else clause in addition to the if clause.

Check for Reversal of the if and else Clauses

We get unexpected results if we flip the if and else clauses.

Therefore, we should check if those clauses are flipped around.

Simplify Complicated Tests with Boolean Function Calls

If we have complex tests in our conditionals, we should put the expressions in a function so that we can just call the function.

The function name also makes everyone clear what the check is doing.

For instance, we can write the following to do that:

const inRange = (min, max, value) => (min <= value && value <= max);
if (inRange(min, max, value)) {
  // ...
} else {
  //...
}

We put the range checking code into a function and then called that.

Now we know that the boolean expressions are checking if value is in the given range of numbers as bound by min and max .

Put the Most Common Cases First

Since the most common cases are encountered the most frequently, we should put them first so most of the time, the less frequently encountered cases don’t have to be checked.

For instance, if letters are the most commonly encountered characters in ou if statement, then we can put that as the first case:

if (isLetter(input)) {
  //...
} else if (isNumber(input)) {
  ///...
} else {
  //...
}

This way, the first case will be the most common one and our program has to run the else clauses less often.

Conclusion

We can write conditional statements that are more efficient by putting the most frequently encountered cases first.

Also, we want to put complex boolean expressions into their own functions to make them clearer.

The normal case should go first since it’s encountered more frequently.

We should also use the right operator for comparison to reduce off-by-one errors.

Categories
JavaScript Best Practices

JavaScript Best Practices— Useless Catch, Concatenation, Escape, Return and More

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at more useless code that we should remove, including useless catch clauses, concatenation, escape characters, useless comments, void operator and return statements.

No Useless catch Clauses

catch clauses that are useless just add extra bloat to our code and don’t do anything useful.

Therefore, we shouldn’t include them in our code. An example includes a catch clause that just throws the exception again:

try {
  foo();
} catch (e) {
  throw e;
}

Instead, we should do something in our catch clause to make it actually useful.

For instance, we may run something to handle the error before rethrowing the error as follows:

try {
  foo();
} catch (e) {
  handleError(e);
  throw e;
}

This way, the catch block actually does something rather than just rethrowing the error, which is redundant because we don’t need to wrap our code with try...catch just to rethrow an error.

The error will be thrown with or without it.

No Unnecessary Concatenation of Strings

It’s useless to concatenate 2 strings together as we do in the following code:

const foo = 'x' + 'y';

In the code above, both operands are strings. We only concatenate if we concatenate expressions like variables or anything else that aren’t constant strings.

We can simplify the example above to:

const foo = 'xy';

It’s much shorter and we don’t have a useless operator in between the 2 strings.

If we have some expression in between the strings or a string with an expression, then they’re also good use of the concatenation operator.

For instance, if we have:

const a = 1;
const foo = 'x' + a;

or:

const a = 1;
const foo = 'x' + a + 'y';

Then they’re good uses of the concatenation operator since we have a variable a within the chain of concatenations.

No Useless Escape Usage

Useless escape characters should be removed from our code.

Examples of this include strings or regex that only have escape characters in them like:

"'";
/!/;

Instead, we should either remove them or make them useful by including other characters in them:

"'foo";
/!foo/;

No Redundant return Statements

In a JavaScript function, a return; statement with nothing after it is useless.

For instance, if we have the following function:

const foo = () => {
  console.log('foo');
  return;
}

Then the return; statement is useless since the function would end with or without the return statement.

Instead, we should either remove it or make it do something like:

const foo = () => {
  return 'foo';
}

Don’t Use the void Operator

The void operator always return undefined . Therefore, it’s pretty useless in most cases in our JavaScript code.

In ES5, undefined values are mutable and the void operator lets us make sure that we get a real undefined value in our code. However, this use case is obsolete now.

Therefore, we shouldn’t use the void operator.

For instance, the following code is bad:

const foo = void 1;

We should just use undefined if we want to use it as follows:

const foo = undefined;

Remove Warning Comments

Warning comments shouldn’t be in production code. It indicates that the code is incomplete or has issues.

Therefore, the comments should be removed and the code should be fixed before going to production.

Examples of warning comments include:

// TODO: fix this
// FIXME: bad code

The comments above shouldn’t be in our code and the issues in the comments should be addressed.

Conclusion

Useless catch clauses are ones that just rethrow the error. We can do that without wrapping our code in try...catch , so it’s just useless code.

We should either remove the try...catch or put some code that handles the error in the catch block.

Redundant return statements are ones that don’t do anything at the end of the function.

They should either return something or be removed.

The void operator always returns undefined , so we should just use undefined and remove the void operator.

Finally, warning comments like todo and fix-me comments should be removed and the issues in the comments should be addressed.

Categories
JavaScript Best Practices

JavaScript Best Practices- Node.js Apps

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at some mistakes that are easy to make in Node apps, including joining paths the wrong way, referencing process.env and more.

No String Concatenation with __dirname and __filename

In Node.js, __dirname and __filename are strings that have the current directory or current script file name respectively.

Many people try to concatenate them like the following to get the full path of a file:

const pathname = __dirname + '/bar.js'

However, the code above is probably since Node apps can be run on different platforms, which have different styles for paths.

For instance, Windows uses backslash a path segment separation, while Linux and macOS use forward slash as separators.

Therefore, concatenation isn’t a reliable way to join paths together. It’s very easy to create an invalid path by concatenation segments together.

Instead, we should use the path.join method to combine segments together as follows:

const path = require('path');
const pathname = path.join(__dirname, 'bar.js');

Then we won’t have to worry about the path structure of the path causing issues when running in different operating systems.

The path.resolve will get the fully qualified path with the full path separators. For instance, we can write:

const path = require('path');
const pathname = path.resolve(__dirname, 'bar.js');

and we get the full path generated by Node.js.

No Reference to process.env

The process.env object is used to store deployment or configuration settings. Putting it everywhere is a maintenance issue because we’re referencing this global dependency everywhere.

It can, therefore, lead to merge conflicts and deployment issues in multiserver setups.

The best practice for storing configuration settings is to store them all in one file and then reference that file instead.

For instance, instead of writing:

const dbPath = process.env.DB_PATH;

We should write:

const config = require("./config");
const dbPath = config.DB_PATH;

No process.exit()

process.exit() is use to immediately stop a Node process and exit the program.

This isn’t good because there may be lots of things running in the background that are needed by the program.

It can stop a progran complete when an error occurs.

For instance, if we have something like the following:

if (error) {
  process.exit(1);
}

Then the program exists immediatelt and there’s no chance for our program to respond to the error is error is truthy.

The better way to raise errors when they occur is to throw an exception. For instance, we can do that as follows:

if (error) {
  throw new Error("error");
}

This way, our program can catch the error and then respond to it. We’ll also see the full stack from the Error object from what’s been called before the error is thrown.

Don’t Use Synchronous Methods in Big Apps

Since Node is single-threade, running synchronous methods means that they have to finish before anything else can run. If it’s a long running process, then this is definitely a problem.

We don’t want one method to hold up our entire app. For instance, if we’re running a web server app, then we definitely shouldn’t have synchronous methods holding up the web server with one request.

However, for scripts that run line-by-line and don’t have multiple parts, synchronous methods are useful for simplifying the code and doing what we want sequentially without much thinking.

For instance, in a big production app, we shouldn’t be running methods like fs.readFileSync as follows:

const fs = require('fs');
const file = fs.readFileSync('foo.txt').toString();

We can instead use methods like readFile rom fs.promise to read a file:

const fs = require('fs').promises;
(async () => {
  const file = await fs.readFile('foo.txt')
  const content = file.toString();
  console.log(content);
})()

This way, we run asynchronous code in a sequence as we do with synchronous code without holding up the rest of our program.

Conclusion

If we want to create strings for file paths, we should use the path.join or path.resolve methods to create full paths.

This way, we won’t have to worry the path structure difference in different OSs.

The best way to store config settings is to put them all in a configuration and then reference everything from that file.

process.exit shouldn’t be used to exit the program when we encounter errors because it end the program abruptly, giving our program no chance of responding.

Finally, other than small scripts, we shouldn’t be using synchronous methods in our Node apps.

Categories
JavaScript Best Practices

JavaScript Best Practices — Imports, Symbols, and Constructors

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at the proper way to import modules, the use of the Symbol constructor, and calling super before referencing this in a child class’s constructor.

No Duplicate Imports

Duplicate imports should definitely not be added. If we import multiple items from the same module, then we should put them all in one statement.

For instance, we shouldn’t write something like the following if we want to import multiple items:

import { foo } from "./module";
import { bar } from "./module";

Instead, we can save some space by writing the following:

import { foo, bar } from "./module";

The more imports we’re importing, the more space we save by consolidating all the imports into one statement if we import multiple members from the same module.

Don’t Use the Symbol as a Constructor

In JavaScript, symbols are used as unique identifiers for methods in objects and classes.

Every Symbol is unique, even if they have the same name. For instance, if we have the following code:

const foo1 = Symbol('foo');
const foo2 = Symbol('foo');
console.log(foo1 === foo2);

Then the console lot output logs false since each symbol returned by the Symbol function is their own instance.

The Symbol function isn’t a constructor. Therefore, we shouldn’t use the new operator with it.

For instance, we shouldn’t write something like the following code:

const a = new Symbol("a");

If we run that, then we’ll get the error message ‘Uncaught TypeError: Symbol is not a constructor’.

Instead, we should just call it directly as follows:

const a = Symbol("a");

The Symbol function should be called directly as a function.

Don’t Use this or super Before Calling super() in Constructors

In JavaScript, if we create a class that extends another class using the extends keyword, then we’ve to call super to call the parent class’s constructor within the child class.

If we reference this before calling super , then we would get a ReferenceError.

For instance, if we have the following classes:

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Cat extends Animal {
  constructor(name, breed) {
    this.breed = breed;
    super(name);
  }
}

const cat = new Cat('jane', 'black')

In the code above, we’ll get the error message ’Uncaught ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor’.

This is because we have to call super to make this defined. The parent constructor has to be called first to finish the configuration of this before the subclass starts the configuration of this .

Superclass doesn’t know about subclasses, so superclasses must be instantiated first.

Therefore, we need to call super before running any code that references this so that we won’t get this error.

The Cat class must be changed so that any code that references this must be moved below the super function’s call.

We should instead write:

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Cat extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
}

const cat = new Cat('jane', 'black')

In the code above, we have the super call that comes before this.breed = breed; in the constructor method.

Now we won’t get any errors when we run the code.

However, code that references this in any other method can be anywhere. For instance, if we have the following code:

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {}
}

class Cat extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  meow() {
    console.log(this.name);
    super.speak();
  }
}
const cat = new Cat('jane', 'black')
cat.meow();

In the code above, we have the speak method in the Animal class. In the Cat class, we have the meow method that references this.name before calling a method from the parent Animal class.

Conclusion

If we import multiple members from the same module, then we should put them all in one statement to save space.

The Symbol function isn’t a constructor function. To create a new symbol, we should create it by calling the Symbol function.

To make sure that our parent class instance is configured properly, then we should call super in our child class’s constructor first and then run any code that references this .

Categories
JavaScript Best Practices

JavaScript Best Practices — Module Imports and Generators

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at the best practices for adding module imports to our JavaScript code and best practices for using iterators and generators.

Multiline Imports Should Be Indented Just Like Multiline Array and Object Literals

Multiline imports should be indented like multiline arrays and object literals.

This means that if we import multiple members from a module and each member is in their own line, then they should have 2 spaces for indentation.

For instance, we write the following to import multiple members in one import statement:

module.js

export const foo = 1;
export const bar = 2;
export const baz = 3;
export const qux = 4;
export const longMember = 5;
export const longMember2 = 5;
export const longMember3 = 5;
export const longMember4 = 5;
export const longMember5 = 5;

index.js

import {
  foo,
  bar,
  qux,
  baz,
  longMember,
  longMember2,
  longMember3
} from "./module";

In the code above, we have many members that are exported from module.js and imported in index.jhs .

Since we have lots of members to import and many of them with long names, we should separate them all into their own line with 2 spaces to indent each import to keep everything neat and not overflowing many people’s screens.

No Webpack Loader Syntax in Module Import Statements

Module import statements for style files should follow the module syntax so that it won’t be coupled to the module bundler of our choice.

This makes sense since module bundlers may change in our project depending on our needs.

Therefore, instead of writing the following code:

import stylesCSS from "style!css!styles.css";

We should instead write:

import stylesCSS from "./styles.css";

The 2nd example is standard syntax for import style files, so we should use that instead.

Do Not Include JavaScript Filename Extensions

We don’t need to include the extension for importing modules, so we shouldn’t include it in our import code.

For instance, instead of writing the following:

module.js

export const foo = 1;

index.js

import { foo } from "./module.js";
console.log(foo);

We should write:

import { foo } from "./module";
console.log(foo);

The JavaScript interpreter is smart enough to identify modules by their name rather than their extensions.

Don’t Use Iterators. Prefer JavaScript’s Higher-Order Functions Instead of Loops Like for-in or for-of

JavaScript arrays have many methods that we can use to manipulate arrays, sorting, searching, mapping values, etc. without having to write a single loop.

Therefore we should take advantage of them instead of writing out loops explicitly.

For instance, if we want to map each entry of an array from one value to another, instead of writing a loop as follows:

const arr = [1, 2, 3];
const result = [];

for (const a of arr) {
  result.push(a ** 2);
}

In the code above, we have an array arr with some numbers in it. Then we have a for...of loop to loop through the values of an array. In the loop body, we called push on the result , which is initially empty, to push the items into the result array.

This is a lot longer than using the map method with a callback to map array entries into items in a new array with new values.

Then we get that the value of result is [1, 4, 9] .

For example, we can use map to do the same thing as follows:

const arr = [1, 2, 3];
const result = arr.map(a => a ** 2);

In the code above, we called the map method with a callback that maps each array entry taking their value and then return the square of each value.

In the end, we get the same value for result , which is [1, 4, 9] . However, it’s much shorter than what we had before with the for...of loop.

Therefore, we should use higher-order array methods like map , reduce , every , some , sort , filter , find , findIndex , etc. as much as possible.

We can chain them if they return an array.

Conclusion

We should use higher-order array methods instead of using loops to do most array processing. They’re much more concise and we can chain them if they return an array.

Imports don’t need an extension for the module part of the statement. If we import lots of members, then we should separate the members in their own lines with 2 spaces for indentation before each member.