Categories
JavaScript Best Practices

JavaScript Best Practices — Modules and Identifiers

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Minimum and Maximum Identifier Lengths

We should make identifier descriptive so they definitely shouldn’t be too short.

It also shouldn’t be too long.

If it’s too short, it’s probably not descriptive enough.

If it’s too long, then it has redundant information and takes up too much space.

So we shouldn’t write:

const x = 5;

since we don’t know what x is.

But we can write something like const numApples = 1; .

Location of Arrow Function Bodies with Implicit Returns

We should put arrows in a logical location.

For instance, we can write:

(foo) => bar;

or

(foo) => (bar);

or

(foo) => bar => baz;

They are conventional and logical.

It’s better than these examples:

(foo) =>
  bar;

or:

(foo) =>
  (bar);

The first examples are more compact than these ones.

Default Imports

We’ve have a default export for a default import to work.

For instance, we should write:

foo.js

export default function () { return 100 }

Then we should import it by writing:

import foo from './foo';

For CommonJS modules, we can write:

foo.js

module.exports = function () { return 100 }

Then we can import it by writing:

const foo = require('./foo');

Repeated Imports and Exports

We can only have one default export in each module.

For instance, we can’t write:

export default class Foo { /*...*/ }

export default class Bar { /*...*/ }

We have to remove one of them.

If we remove one:

export default class Foo { /*...*/ }

Then we can write:

import Foo from './foo';

Export Statements that Come Before Other Statements

We shouldn’t have export statements that come before other statements.

For instance, we don’t want to fix statements like:

const bool = true

export default bool

const str = 'foo'

Moving all exports to the bottom would make it easier to read.

Use of File Extension within the Import Path

When we write imports, we don’t need an extension for JavaScript files.

For instance, instead of writing:

import bar from './foo/bar.js';

We write:

import bar from './foo/bar';

However, we should put the extension for JSON import.

For instance, we can write:

import bar from './bar.json';

We may also do the same thing for JavaScript files that don’t have the .js extension.

For instance, we can write:

import Component from './Component.jsx';

for JSX files.

Putting Imports First

We should put import statements first.

For instance, we should put them at the top.

Instead of writing:

import foo from './foo'

doSomething(foo)

import bar from './bar'

We write:

import foo from './foo';
import bar from './bar';

doSomething(foo);

Grouping Exports

We can group exports together to make finding them easier.

For instance, we can write:

const first = 'foo';
const second = 'bar';

export {
  first,
  second,
}

With CommonJS modules, we can write:

const test = {};
test.foo = true
test.bar  = true

module.exports = test;

We also grouped the exports together.

Named Exports

We can import named module members in various ways.

Given that we have:

export const foo = "foo"

We can write:

import { foo } from './foo'

Also, we can write:

export { foo as bar } from './foo'

to export it as bar .

We can also import it as bar:

import { foo as bar } from './foo'

Newline After Import

After a group of imports, we can put a new line to separate the imports from the rest of the code.

For instance, we can write:

import defaultExport from './foo'
import { bar } from 'bar'

const foo = 'bar';

or:

const foo = require('./foo')
const bar = require('./bar')

const baz = 1;

for CommonJS modules.

No Absolute Paths

We shouldn’t use absolute paths for imports because it’ll only work on the current computer’s current folder strucutre.

If we move it anywhere, it won’t work.

So we shouldn’t write:

import f from '/foo';
import bar from '/some/path';

or:

const f = require('/foo');
const bar = require('/some/path');

Instead, we write:

import f from './foo';
import bar from './some/path';

or:

const f = require('./foo');
const bar = require('./some/path');

Conclusion

We should write our imports and exports properly.

Never import with absolute paths.

And we should group them together.

Identifier lengths shouldn’t be too long or short.

Categories
JavaScript Best Practices

JavaScript Best Practices — Spacing, Lines, and Call

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Named Function Expressions

Names in function expressions aren’t very useful.

If we set the function to a variable, we can’t use it with that name.

We also don’t need it much in IIFEs.

So we can write:

Foo.prototype.bar = function() {};

instead of:

Foo.prototype.bar = function bar() {};

Also, we can write:

(function() {
    // ...
}())

instead of:

(function bar() {
    // ...
}())

Unless we need the name inside the function, it’s not that useful.

Consistent Use of Either Function Declarations or Expressions

We can keep to one function style for consistency.

So either we stick with:

function doWork() {
  // ...
}

or:

const doWork = function() {
  // ...
};

Arrow functions can only use the second style.

Line Breaks Between Arguments of a Function Call

We only need line breaks for arguments and function calls are only needed for long expressions or argument lists.

For instance, we can write:

foo(
  "foo",
  "bar",
  "three"
);

or:

foo("one")

We just keep line length around 100 characters or less.

Consistent Line Breaks Inside Function Parentheses

We should have consistent line breaks inside function parentheses.

For instance, we can write:

function foo(
  bar,
  baz
) {}

or:

const foo = function(
  bar, baz
) {};

For short functions, we can keep the signature all in one line.

Other than that, we break it into multiple lines.

Spacing Around the * in Generator Functions

We should keep the spacing around the * in generator functions.

For instance, we can write:

function* generator() {
  yield "foo";
  yield "bar";
}

or:

function * generator() {
  yield "foo";
  yield "bar";
}

or:

function *generator() {
  yield "foo";
  yield "bar";
}

We just have to stick with one.

Return Statement Should be Present in Property Getters

The whole point of getters is to return something.

Therefore, we should make sure that’s done in getters.

For instance, we should write:

const obj = {
  get name() {
    return "james";
  }
};

or:

Object.defineProperty(obj, "age", {
   get() {
     return 100;
   }
 });

instead of:

const obj = {
  get name() {
    return;
  }
};

or:

Object.defineProperty(obj, "age", {
   get() {

   }
 });

or:

class P {
  get name() {

  }
}

Put require() on the Top-Level Module Scope

We should put our require calls at the top level of the module scope.

For instance, we should write:

const fs = require("fs");

instead of:

function foo() {
  if (condition) {
    const fs = require("fs");
  }
}

Dynamically requiring things is confusing,

So we shouldn’t do it.

Require Grouped Accessor Pairs in Object Literals and Classes

Having setters that don’t have getters isn’t very useful since we can’t get the value that’s set.

Therefore, we should have getters for any setters.

For instance, we should write:

const obj = {
  get a() {
    return this.val;
  },
  set a(value) {
    this.val = value;
  },
  b: 1
};

instead of:

const obj = {
  set a(value) {
    this.val = value;
  },
  b: 1
};

We can get the value of a in obj with the getter.

Guarding for for-in

We should check for inherited properties if we loop with for-in since it enumerates inherited properties.

Instead of writing:

for (key in foo) {
  doWork(key);
}

We write:

for (key in foo) {
  if (Object.prototype.hasOwnProperty.call(foo, key)) {
    doWork(key);
  }
}

or:

for (key in foo) {
  if ({}.hasOwnProperty.call(foo, key)) {
    doSomething(key);
  }
}

We use Object.prototype.hasOwnProperty.call or {}.hasOwnProperty.call instead of foo.hasOwnProperty so that it still works if hasOwnProperty has been overwritten.

hasOwnProperty checks if a property is a noninherited property.

Callback Error Handling

We should check for errors in callbacks if they exist.

For instance, we can write:

function foo(err, data) {
  if (err) {
    console.log(err.stack);
  }
  doWork();
}

If it’s a Node style callback, err will be populated with the error, which we should handle in case it’s thrown.

Conclusion

We should handle errors in callback.

Naming function expressions is redundant.

Line breaks and spacing should be consistent.

Categories
JavaScript Best Practices

JavaScript Best Practices — 12 Useless Statements You Should Avoid

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Dangling Underscores in Identifiers

Dangling underscores are used for identifying private variables whether they’re actually private or not.

It’s best not to use this since if they’re not private, people may think it’s private.

So instead of writing:

foo._bar();

We write:

foo.bar();

No Confusing Multiline Expressions

We should separate statements and expressions with semicolons when they need to be separated.

For instance, we shouldn’t code like:

const foo = bar
[1, 2, 3].forEach(log);

We don’t know if the 2 lines should be together or not.

Instead, we write:

const foo = bar;
[1, 2, 3].forEach(log);

to separate them clearly.

No Unmodified Conditions of Loops

We shouldn’ have unmodified conditions in loops.

This creates infinite loops, which probably don’t want.

For instance, we shouldn’t write:

while (item) {
  doWork(item);
}

Instead, we write:

for (let j = 0; j < items.length; j++) {
  doWork(items[j]);
}

No Ternary Operators when Simpler Alternatives Exist

We shouldn’t use ternary expressions when there’re simpler alternatives.

For instance, we shouldn’t write:

const isOne = answer === 1 ? true : false;

Instead, we write:

const isOne = answer === 1;

We don’t need to write true and false explicitly.

No Unreachable Code After return, throw, continue, and break Statements

We shouldn’t have unreachable code after return , throw , continue , and break statements since they’ll never be run.

For instance, instead of writing:

function foo() {
  return true;
  console.log("end");
}

We write:

function foo() {
  return true;
}

No Control Flow Statements in finally Blocks

We shouldn’t have control flow statements like return statements in finally blocks.

Instead, we should put them elsewhere.

Control flow statements are suspended until finally runs.

So the control flow statements in try or catch will be overwritten with the one in finally .

Therefore, instead of writing:

try {
  return 1;
} catch (err) {
  return 2;
} finally {
  return 3;
}

We write:

try {
  return 1;
} catch (err) {
  return 2;
} finally {
  console.log('finally');
}

No Negating the Left Operand of relational Operators

We shouldn’t negate the left operand of relational operators since they only negate the operand and not the whole expression.

For instance, instead of writing:

if (!key in obj) {
  // ...
}

We write:

if (!(key in obj)) {
  // ...
}

No Unused Expressions

If an expression isn’t used, then we should remove them.

For instance, we shouldn’t write:

{0}

or:

c = a, b;

instead, we write:

f()

or:

a = 0

etc.

No Unused Variables

If we have unused variables, we should remove them.

So if we have:

var x;

and it isn’t used anywhere else, then we should remove them.

No Early Use of Variables and Functions

We shouldn’t use variables or functions before they’re declared in our code.

For instance, we can write:

console.log(a);
var a = 10;

or:

f();
function f() {}

We can use function declarations and variables before they’re declared.

Function declarations can be called, but variables will be undefined if they’re used before they’re declared.

To remove confusion, we should write:

var a = 10;
console.log(a);

and:

function f() {}
f();

No Unnecessary .call() and .apply()

We shouldn’t use call and apply unnecessarily.

If we don’t change the value of this , then we shouldn’t use them.

Instead, we just call it directly.

So instead of writing:

foo.call(undefined, 1, 2, 3);
foo.apply(undefined, [1, 2, 3]);
obj.foo.call(obj, 1, 2, 3);
obj.foo.apply(obj, [1, 2, 3]);

We write:

foo(1, 2, 3)
obj.foo(1, 2, 3)

No Unnecessary catch Clauses

If our catch clause only rethrows an exception, then we don’t need them.

So we shouldn’t write:

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

try {
  doWork();
} catch (e) {
  throw e;
} finally {
  cleanUp();
}

We write:

try {
  doWork();
} catch (e) {
  handleError(e);
}
try {
  doWork();
} catch (e) {
  handleError(e);
} finally {
  cleanUp();
}

Conclusion

We shouldn’t write useless statements.

They make our code more complex and are redundant.

Categories
JavaScript Best Practices

JavaScript Best Practices — Shadowing Variables and Spacing

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Variable Declarations from Shadowing Variables Declared in the Outer Scope

We shouldn’t have variable declarations from shadowing variables declared in the outer scope.

For instance, we shouldn’t write things like:

var a = 3;

function b() {
  var a = 10;
}

Instead, we write:

function b() {
  let a = 10;
}

We don’t want to declare them twice since the a outside the function is in the global scope, which means they cause confusion.

No Shadowing of Restricted Names

We shouldn’t assign anything to variables with restricted names.

For instance, we shouldn’t write statements like:

const undefined = "foo";

Instead, we write:

const foo = "foo";

No Spacing Between Function Identifiers and Their Applications

We shouldn’t have spacing between function identifiers and their invocation.

So we shouldn’t have things like:

fn ()

fn
()

Instead, we write:

fn()

No Sparse Arrays

Sparse arrays have empty slots.

These may be mistakes, so we may want to avoid them.

For instance, instead of writing:

const items = [,,];

We write:

const colors = [ "red", "green"];

Node Synchronous Methods

If we’re writing an app, we definitely shouldn’t be using synchronous methods.

They hold up the whole app with one operation since Node is single-threaded.

They’re still good for scripts.

So instead of writing:

function foo(path) {
  const contents = fs.readFileSync(path).toString();
}

We write;

async(function() {
  // ...
});

in apps.

No Tabs

Tabs aren’t as good as spaces for spacing since they may differ between text editors and platforms.

So instead of tabs, we write spaces.

We can replace tabs automatically with spaces.

No Template Literal Placeholder Syntax in Regular Strings

The template literal placeholder syntax in regular strings is useless.

Therefore, we should only use them in template strings.

For instance, instead of writing:

const greet = "Hello ${name}!";

We write:

const greet = `Hello ${name}!`;

Ternary Operators

Ternary operators are useful for conditionally returning data in line.

For instance, we can write:

const foo = isBar ? baz : bar;

It’s short and not too hard to read.

No Use of this/super Before Calling super() in Constructors

We should call super before assigning this properties in the subclass constructor.

For instance, instead of writing:

class A extends B {
  constructor() {
    this.a = 0;
    super();
  }
}

We write:

class A extends B {
  constructor() {
    super();
    this.a = 0;
  }
}

We’ll get an error with the first example.

Restrict What can be Thrown as an Exception

We show throw Error instances when using throw ,

For instance, instead of writing:

throw "error";

We write:

throw new Error("error");

The Error object has more useful things like the stack trace and message that isn’t in other objects or values.

Trailing Whitespace at the End of Lines

We shouldn’t have trailing whitespace at the end of the lines.

We should remove trailing whitespace if they’re there.

No Undeclared Variables

We shouldn’t use undeclared variables in our code.

Without strict mode, they’re treated as global variables.

If strict mode is on, it’ll be treated as an error.

So w shouldn’t write things like:

const bar = a + 1;

if a isn’t declared.

Instead, we write:

const a = 2;
const bar = a + 1;

No Initializing to undefined

Variables that are declared without a value are initialized to undefined by default, so we don’t need to set explicitly.

For instance, we shouldn’t need to write:

let foo = undefined;

Instead, we write:

let foo;

No Use of undefined Variable

We shouldn’t set a value to undefined .

It’ll throw an error under strict mode.

For instance, we shouldn’t write code like:

const undefined = "hi";

It makes no sense to assign undefined to a value.

Conclusion

We shouldn’t assign restricted identifiers with values.

Also, we should make sure that function calls and code are formatted in conventional ways to avoid confusion and other issues.

Node synchronous methods are good for scripts but not apps.

Categories
JavaScript Best Practices

JavaScript Best Practices — Mutation, Arguments, and Function Names

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Don’t use arguments

arguments is a special variable that lets us get the arguments of a function.

We shouldn’t use it since there are better alternatives.

It also doesn’t work with arrow functions.

For instance, instead of writing:

function sum() {
  const numbers = Array.prototype.slice.call(arguments);
  return numbers.reduce((a, b) => a + b);
}

We write:

function sum(...numbers) {
  return numbers.reduce((a, b) => a + b);
}

The rest operator returns the arguments as an array.

Use of delete

delete removes a property from an object.

It mutates the object, so we may not want to use it.

For instance, instead of writing:

delete foo.bar;

We write:

const _ = require('lodash/fp');

const fooWithoutBar = _.omit('bar', foo);

to return a new object without the bar property.

Object.assign() with a Variable as First Argument

If we pass in a variable as the first argument with Object.assign , then we mutate the variable.

This means that it’s better not to set the first argument with a variable.

Instead, we put an object.

For instance, we can write:

const foo = Object.assign({}, a, b);

instead of:

const a = { foo: 1, bar: 2 };
const b = { bar: 3 };
Object.assign(a, b);

Mutating Methods

It’s easy to do accidental mutations, so we shouldn’t use mutating methods too much.

For instance, array methods like push , pop , unshift , etc. mutate arrays, so we may want to consider alternatives that don’t do mutation.

We can use spread instead of push .

For instance, we can write:

const bar = [...arr, 1];

instead of push .

pop can be replaced with slice :

const baz = arr.slice(-1);

unshift can be replaced with spread:

const bar = [1, ...arr];

sort is harder to replace so we may want to keep using it, but we stop using original array.

It returns the sorted array.

Mutating Operators

We should be careful with mutation operators.

Operators like += does assignment to update an existing variable.

This means that we can accidentally modify them easily.

We should also be careful with -= , *= , /= , %= , ++ or -- .

The placement of ++ or -- also matters so we should be careful with that.

It does both assignment and returns a value.

++ coming after a variable returns the original value.

++ coming before a variable returns the new value.

This is the same with -- .

Use of Proxy

Proxies commit side effects, so we may not want to use them.

For instance, instead of writing:

const handler = {
  get(target, key) {
    return Math.max(target[key], 0);
  }
};
const object = new Proxy(variable, handler);
object.a;

We make a function instead:

const negativeProp = (target, key) => {
  return Math.min(target[key], 0);
}
negativeProp(object, 'a');

Spacing Between Function Identifiers and their Invocations

We can add spacing between function identifiers and their invocations.

For instance, we should write:

alert('Hello');

instead of:

alert ('Hello');

Extra spaces aren’t very useful.

Function Names should Match the Name of the Variable or Property to which they are Assigned

It’s useless to have a function name if we’re going to assign it to a variable.

But if we have them, at least the names should match.

For instance, if we have:

const foo = function bar() {};

Then we call the function with foo .

Instead, we should write:

const foo = function foo() {};

or:

const foo = function() {};

This is the same with properties.

For instance, instead of writing:

obj['foo'] = function bar() {};

or:

obj['foo'] = function() {};

We write:

obj['foo'] = function foo() {};

or:

obj['foo'] = function() {};

We should remove them or make them match.

Conclusion

Names are redundant if we assign a function to a variable or property.

The arguments object shouldn’t be used.

We should be careful with mutating methods.

Also, we should be careful with mutation operators.

Spacing of function calls are redundant.