Categories
JavaScript Best Practices

JavaScript Best Practices — Modules

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 best practices for importing and exporting from JavaScript modules.

Only Import From a Path in One Place

If we import multiple members from one module, then all members should be imported in one import statement.

For instance, instead of writing the following code to import members:

module.js

export const foo = 1;
export const bar = 2;

index.js

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

In the code above, we have 2 lines to import 2 different members from the same module. That’s no good since we have lots of duplicate code in one place.

Instead, we should just put them all in the same module to save space and remove duplicate code.

For example, we can instead write the following:

module.js

export const foo = 1;
export const bar = 2;

index.js

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

In the code above, we imported the members of module.js in index.js all in one line. As we can see, we saved lots of typing and duplication since we combined the 2 lines into one.

Do Not Export Mutable Bindings

We shouldn’t export mutable entities because we don’t want to accidentally change them inside the module that it’s export from. For instance, if we have the following code:

module.js

export let foo = 1;
foo = 3;
export const bar = 2;

index.js

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

In the code above, we exported the foo member from module.js and then change the value of foo later.

Then when we import foo in index.js , we see that the value 3 is logged for foo .

Therefore whenever we change the value by reassigning it, the new value will be exported.

Therefore, we shouldn’t export members that have been defined with let since these changes may be hidden if we import it from a library and not code that we edit usually.

If foo is defined with const then we wouldn’t be able to reassign it to a new value after we export it.

In Modules With a Single Export, Prefer Default Export Over Named Export

If we have only one member to export in a module, then we should just export it as a default export.

The advantage of that is that we can name it whatever we want when we import it without the as keyword.

For instance, instead of writing the following:

module.js

export const bar = 2;

index.js

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

We can write the following instead:

module.js

const bar = 2;
export default bar;

index.js

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

In the second example, we exported bar as a default export. Then in index.js , we can import it with the name foo or any name instead of the name bar without using the as keyword.

This is convenient to avoid name clashes with imports if we have lots of imports.

Put All Imports Above Non-Import Statements

Imports should be above non-import statements since this makes everyone clear where the exports are.

For instance, instead of writing the following code:

bar.js

export const bar = 1;

module.js

export const foo = 2;

index.js

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

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

In the code above, we imported items from 2 different modules in index.js and the 2 import statements are far from each other.

We have the foo import separated from the bar import with a console.log call.

Import statements being scattered makes finding them hard.

Instead, we should write the following:

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

console.log(foo, bar);

to group all the import statements at the top of our code file.

Conclusion

We should group import statements together so that they can easily find the imports from the top of the code.

If we have only one member to export, then we can just export it as a default.

Also, we should export mutable bindings since they can be changed after they’re exported.

Categories
JavaScript Best Practices

JavaScript Best Practices — Functions

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

In this article, we’ll take a look at some best practices for defining and using functions.

Never Declare a Function in a Non-Function Block

A function should never be defined in a non-function block like a if block or within loops.

Functions within loops tend to cause errors since functions create a closure around the loop.

For instance, if we have the following code:

const funcs = []
for (var i = 0; i < 10; i++) {
  funcs[i] = () => i
}

for (const f of funcs) {
  console.log(f());
}

The code above would log 10 10 times because we declared the loop variable with var , so the last value, which is 10, will be returned in each function that’s in the funcs array.

We don’t want that, so we shouldn’t declare functions on the fly inside non-function blocks.

Use Function Expressions Inside Blocks

Function expressions can’t be used before they’re defined. This means that if they’re in blocks, then they can’t be used before they’re defined within the block.

Also, function declarations within blocks aren’t valid JavaScript syntax, so they shouldn’t be defined inside blocks.

If we want to define a function within a block, then we’ve to use function expressions.

For instance, we can define one within an if block as follows:

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

The code above is valid JavaScript syntax, so we used const to define it so it’s only available within the block.

Therefore, we should remember to define functions as function expressions instead of using function declarations in blocks.

Never name a parameter arguments

We should never name function parameters with the name arguments since arguments is a special object that’s available within a traditional function.

If we have a parameter arguments , then that’ll overwrite the value of arguments with the value of the arguments parameter.

For instance, if we have the following code:

function foo(arguments) {
  console.log(arguments);
}

foo(1)

Then the value of arguments is 1. On the other hand, if we have the following code:

function foo() {
  console.log(arguments);
}

foo(1)

Then we get the actual arguments object, which is:

Arguments [1, callee: ƒ, Symbol(Symbol.iterator): ƒ]

according to the console log output. It has the argument that we passed in and an iterator to iterate through the items or convert it to an array via Array.from or the spread operator.

Therefore, we should name our parameter something other than arguments .

Never Use arguments, Use Rest Syntax ... Instead

With the introduction of the rest operator, the arguments object is pretty much useless.

The rest operator takes the arguments that we passed in and put them into an array if it has been assigned to a parameter. It works with both arrow functions and traditional functions

On the other hand, the arguments object is only available within traditional functions and it’s an iterable array-like object rather than an array.

Therefore, to do anything useful with it, we’ve to convert it to an array, which is inconvenient.

For instance, instead of writing the following:

function foo() {
  console.log(arguments);
}

foo(1)

We should instead write:

function foo(...args) {
  console.log(args);
}

foo(1)

args would be an array with value 1 inside it.

Likewise, we can use arrow functions as follows:

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

foo(1)

And we get the same result as before.

We can call any array method or do any array operations with rest parameters. For instance, we can write:

const addAll = (...args) => args.reduce((a, b) => a + b, 0)

addAll(1, 2, 3);

to add all the arguments that are passed and add them together with the reduce method.

That’s something that we can’t do with the arguments object directly.

Conclusion

Rest parameters are much better than the arguments object for getting arguments that are passed into a function.

It works with both traditional and arrow functions.

We should never name a parameter with the name arguments since it clashes with the arguments object.

Declaring a function within a non-function isn’t valid syntax, so we shouldn’t do it.

We should only create functions as function expressions inside blocks.

Categories
JavaScript Best Practices

JavaScript Best Practices — Function Length and Parameters

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at the best practices when designing functions, including the ideal length and the number of parameters it should take.

Use Opposites Precisely

If 2 functions do opposite things, then they should be named precisely

For instance, if we have 2 functions to increment and decrement and number respectively, then they should be named as such.

They should be named increment and decrement to describe what they do.

How Long Can a Function Be?

Function length are inversely correlated to the number of errors per line of code according to Basili and Perricone study.

Another study from Shen et. al. mentioned that function size isn’t correlated with errors per line.

But structural complexity and amount of data were correlated with errors.

We should just let functions grow into their size organically. And we should only put operations together if they must be put together.

However, if a function is longer than 200 lines, then we should think we can break them up into smaller functions.

This is because if it’s longer than 200 lines, then a function would be harder to understand for most people.

Function Parameters

When we’re adding parameters into a function, we should take into account some guidelines so that we make them easier to understand and avoid mistakes.

If Several Function Use Similar Parameters, Put the Similar Parameters in a Consistent Order

If we have multiple functions that have similar parameters, then we should put them in the same order in each function.

For instance, instead of writing:

const calcVolume = (length, width, height) => {
  //...
}

const calcArea = (width, length) => {
  //...
}

We should write:

const calcVolume = (length, width, height) => {
  //...
}

const calcArea = (length, width) => {
  //...
}

This way, we won’t be confused about the order of the parameters when we pass them in.

Use All the Parameters

We should use all parameters that are in the function signature. If it isn’t used often, then we should take them out.

Unused parameters cause more errors since there’s more to think about.

Put Status or Error Variables Last

If we have status or error variables, then we should put them last since they’re output only.

For instance, we write:

const callback = (res, err) => {
  //...
}

err is output only, so it should go last.

Don’t Use Function Parameters as Working Variables

We shouldn’t mutate parameters. This way, we won’t be accidentally modifying the value outside if it’s passed by reference.

For instance, we shouldn’t have code like:

const fn = (input) => {
  input++;
  //...
}

Instead, we write:

const fn = (input) => {
  let count = input;
  count++;
  //...
}

Always assign it to a variable and then apply operations to that instead.

Limit the Number of a Function’s Parameters to About 7

The fewer parameters we have in our code the better.

It’s easier to read and harder for us to make mistakes passing them in.

If we have a dozen parameters, then anyone would get confused with the order of the parameters and the data type of each.

This is worse since JavaScript doesn’t provide any data type checking built-in at compile time.

Use Named Parameters

We can mimic named parameters by using objects as parameters and the destructuring syntax.

For instance, we can write:

const calcArea = ({
  length,
  width
}) => {
  return length * width;
}

calcArea({
  length: 1,
  width: 2
});

Then we don’t have to worry about the order that length and width are passed in and we can see what we passed in for each parameter.

Conclusion

There’re a few things to consider when we’re defining functions.

The lengthy matters since we don’t want functions that are so long that they’re hard to understand.

Also, we want to reduce the number of parameters in our function to reduce the chance of mistakes.

We also want to destructure object parameters to mimic named parameters so that we can see the name of the property we pass in.

Categories
JavaScript Best Practices

JavaScript Best Practices- Bad Variable Declarations and Better Node Code

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 how to declare variables the right way and writing better Node apps by cleaning up require calls and handling errors.

Don’t Use Restricted Names as Identifiers

We shouldn’t use restricted names like undefined , NaN , or Infinity as identifiers for variable names, functions, etc.

It confuses the user by thinking that we’re actually doing the assignment to those values when that’s not actually being done.

For instance, if we have:

var NaN = "bar";

Then that’s deceptive since most people would assume that 'bar' is set as the value of undefined .

However, when we log the value of undefined , we still get undefined .

JavaScript’s strict mode would prevent something like the code above from running.

Also, we’ll also get errors if we declare the undefined variable with let or const .

Do Not Use undefined As a Variable

Theoretically, since the JavaScript undefined value is a global variable, we may be able to overwrite it with some other value.

However, we can write something like the following:

var undefined = "bar";

and not get errors if strict mode if off. No assignment was actually done, but the code runs.

Therefore, we shouldn’t use undefined as a variable name in any situation since it doesn’t do the assignment as we expected it to. If it does, then it would be scarier since we expect undefined to be undefined .

No Unused Variables

Unused variables are dead and useless code. Since they aren’t being used anywhere, they should be removed from our codebase.

Declarations like:

let x;

that isn’t referenced anywhere else should be removed.

If we declare a variable, we should either use it or remove it.

Don’t Use Variables Before it’s Defined

If we declare a variable with var , then we can use it before it’s declared because the variable declaration itself is hoisted.

For instance, we can use it as follows if we have a variable declared with var:

console.log(foo);
var foo = 1;

In the code above, we would see that foo is undefined from the console log output since the variable was hoisted, but the value assignment is still done in its original position.

This is very confusing for lots of people, so we shouldn’t use variables before they’re declared.

Better yet, we should use let and const to declare variables and constants respectively so that we don’t have about issues like this.

let and const variables and constants are block-scoped so that their scope stays inside the block.

Also, they aren’t hoisted so they can’t be referenced before they’re declared. Therefore, we’ll get an error if we try to reference it before it’s declared.

Put require() On the Top of a Module File

require should be added to the top of a module file. It’s cleaner and we get all the imported modules members all in one place.

This way, we don’t have to worry about using module members that haven’t been imported yet since if we see them all at the top, then we know that they’re imported right away.

Therefore, the following code:

const fs = require("fs");

const readFile = (fileName) => {
  const file = fs.readFileSync(fileName);
  return file;
}

readFile('foo.txt');

is better than:

const readFile = (fileName) => {
  const fs = require("fs");
  const file = fs.readFileSync(fileName);
  return file;
}

readFile('foo.txt');

Even though both are valid imports. It’s just that the first example is cleaner as we put all the require calls in one place.

Do Callback Error Handling

If a callback function has the err parameter. Then we should probably do something to check if anything is set as the value for the err parameter before proceeding.

We can handle this by writing the following:

const fs = require("fs");

const readFile = (fileName) => {
  fs.readFile(fileName, (err, data) => {
    if (err) {
      throw err;
    }
    console.log(data);
  });
}

readFile('foo.txt');

The callback we have above, we wrote:

if (err) {
  throw err;
}

to throw the err object if an error is encountered. Then we can handle it somewhere else.

Conclusion

Restricted names like undefined or NaN shouldn’t be used as identifiers for anything like variables or functions.

In Node apps, require should be on the top of the file to make the require calls easier to read since they aren’t scattered everywhere.

Finally, if we have errors in our callbacks, we should handle them.

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrow Functions

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 how to define and use arrow functions in the cleanest way possible.

If an Expression Spans Multiple Lines, Then Wrap it in Parentheses to Make it More Readable

If we have an arrow function that has a long JavaScript expression that spans multiple lines, then we should wrap the expression in parentheses.

This applies to arrow functions that have no curly braces. For instance, if our arrow function returns an object, then we should write the following:

const foo = () => ({  
  a: 1  
})

In the code above, we wrapped our object expression in parentheses so that we can return the object when we call the foo function.

Wrapping the object in parentheses lets us return it instead of treating it as curly braces for the function body.

Therefore, we should wrap expressions in curly braces.

Always Include Parentheses Around Parameters for Clarity and Consistency

Arrow function signatures may skip the parentheses if the signature only has one parameter. Otherwise, we must include the parentheses.

However, even though we can skip the parentheses in the signature, we probably want to include them to make our function signature more clear and to keep the code consistent with other function definitions.

For instance, we write the following code to include the parentheses in the function signature:

const foo = (a) => a + 1

Avoid Confusing Arrow Function Syntax (=>) With Comparison Operators (<=, >=)

The fat arrow in an arrow function looks like the comparison operator since they’re both made of an equal sign and the greater or less than sign.

To make arrow functions easier to distinguish from comparison expressions, we should put parentheses around our expressions.

For instance, we can write the following code for an arrow function:

const foo = (a) => (a <= 1);

We wrapped our function body and signature with parentheses so that we know that we have an arrow function.

From the parentheses, we can also clearly see that the comparison expression is a <= 1 . If we skip the parentheses, then we’ll have a hard time distinguishing between the function code and the comparison expressions.

So if we have something like the following:

const foo = a => a <= 1;

Then it’s very hard to tell where the function arrow is and where the comparison arrow is.

For longer functions, we should also have curly braces to delimit the function body as follows:

const foo = (a) => {  
  return a <= 1;  
}

In the code above, we wrapped our function body around curly braces to make everyone clear that it’s the function body.

Enforce the Location of Arrow Function Bodies With Implicit Returns

With implicit returns of an arrow function, we have to keep the position of the expression that we’re returning is consistent. It should start in the first line of the arrow function.

However, the remaining expression can be in lines below it. For instance, we can write functions like the following code:

const foo = (a) => a + 1;  
const bar = (a) => (a + 1);  
const baz = (a) => (  
  a + 1  
)

In the code above, we have the implicit return expression in the first line. We can put it in the first line in several ways.

If we wrap the expression we return in parentheses, then we can move our expression to the second line as we have in the baz function.

However, the most important thing is to keep the start of the expression we return in the first line, which includes parentheses if added.

Conclusion

With arrow functions, we have to be careful about implicit returns. The expression we return must start from the first line.

If the expression we return spans multiple lines, then it must be wrapped in parentheses.

Also, to avoid confusion with comparison operators like greater than or less than, we should wrap comparison expressions in parentheses or if we have multiline arrow functions, we wrap the whole body with curly braces.