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.

Categories
JavaScript Best Practices

JavaScript Best Practices — Interfaces, Tests, Async Code, and Comments

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.

Interface Segregation Principle

Even though JavaScript doesn’t have explicit interfaces, we shouldn’t make outside code depend on interfaces they don’t use.

We should create interfaces that they all can use.

For instance, instead of writing classes with lots of methods, we break them up into smaller ones.

This also keeps them from having more than one responsibility.

Testing

Testing is important to keep the quality of the software.

We can automate most of them to make our work easier.

There is something that we should be aware of.

Single Concept Per Test

We should keep out tests simple by testing one concept in each test.

For instance, instead of writing:

import assert from "assert";

describe("Rectangle", () => {
  it("handles dimension changes", () => {
    let rectangle;

    rectangle = new Rectangle(1, 2);
    rectangle.setWidth(30);
    assert.equal(30, rectangle.getWidth());

    rectangle = new Rectangle(1, 2);
    rectangle.setHeight(30);
    assert.equal(30, rectangle.getHeight());
  });
});

We write:

import assert from "assert";

describe("Rectangle", () => {
  it("changes height", () => {
    const rectangle = new Rectangle(1, 2);
    rectangle.setWidth(30);
    assert.equal(30, rectangle.getWidth());
  });

  it("changes width", () => {
    const rectangle = new Rectangle(1, 2);
    rectangle.setHeight(30);
    assert.equal(30, rectangle.getHeight());
  });
});

We create 2 tests instead of one.

Async

JavaScript code is async, so we’ll have to write async callbacks a lot.

Use Promises, not Callbacks

We should use promises to do async operations.

It’s especially useful if we do have to do multiple operations.

For instance, we write:

import { writeFile, readFile } from "fs";

readFile(
  `'./foo.txt',`
  (`err, data)`) => {
    if (err) {
      console.error(err);
    } else {
      writeFile("./bar.txt", `data`, writeErr => {
        if (writeErr) {
          console.error(writeErr);
        } else {
          console.log("File written");
        }
      });
    }
  }
);

We write:

const fs = require("fs");
const { promisify } = require("util");

const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);

`async function main() {
  const data = await writeFile("/foo.txt");
  await writeFile("./`bar.txt`", data);
}`

We use async and await to chain the promises.

util.promisify convert some async functions to promises.

Error Handling

Error handling is important.

We shouldn’t ignore caught errors.

For instance, instead of writing:

try {
  doSomething();
} catch (error) {
  console.log(error);
}

We write:

try {
  doSomething();
} catch (error) {
  console.error(error);
  notifyError(error);
}

We make the error obvious with console.error or another function or both.

Don’t Ignore Rejected Promises

We shouldn’t ignore promises that are rejected.

For instance, instead of writing:

getdata()
  .then(data => {
    doSomething(data);
  })
  .catch(error => {
    console.log(error);
  });

We should write:

getdata()
  .then(data => {
    doSomething(data);
  })
  .catch(error => {
    console.error(error);
    notifyError(error);
  });

We make the error obvious with console.error or another function or both.

Formatting

We shouldn’t have to argue over formatting.

It’s a waste of time.

Use Consistent Capitalization

We should use consistent capitalization for our code.

For instance, we capitalize names of constants:

const DAYS_IN_WEEK = 7;

Also, we use camel case for most other identifiers:

function eraseDatabase() {}

And we use Pascal case for constructors or classes:

class Animal {}

Function Callers and Callees Should be Close

We should keep the callers and callees close together so we don’t have to jump around to read our code.

For instance, we can write:

class Foo {
  foo(){}

  bar() {
    this.foo();
  }
}

We keep them together as close as we possibly can.

This is better than:

class Foo {
  foo(){}

  baz(){}

  qux(){}

  bar() {
    this.foo();
  }
}

We’ve to jump through multiple methods to read the stuff.

Comments

We should comment on things that can’t be told by the code.

For instance, instead of writing:

// loop through objects
for (const item of items) {
  //...
}

We can omit it since it’s the code already tells us what it does:

for (const item of items) {
  //...
}

Conclusion

We should have small interfaces so that we don’t have to depend on big interfaces where the items aren’t completely used.

Tests should have a single concept.

Use promises for async code.

Function callers and callees should be close together.

And we shouldn’t write comments to repeat what is said in the code.

Categories
JavaScript Best Practices

JavaScript Best Practices — this, Braces, and Switch

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.

Consistent Name for This

If we assign this to a variable, we should set it to a consistent name.

We can pick that , self , or me .

For instance, we write:

const that = this;
jQuery('p').click(function(event) {
  that.setFoo(42);
});

or:

const  self = this;

or:

let me;

function f() {
  me = this;
}

Sticking with one name reduce confusion.

Calls of super() in Constructors

We need to call super before assigning instance variables to values.

For instance, we write:

class A extends B {
  constructor() {
    super();
    this.foo = 'bar';
  }
}

We call super before the foo assignment.

This way, we won’t get errors.

We need to call super for subclasses.

We don’t need it for parent classes:

class A {
  constructor() {}
}

Following Curly Brace Conventions

We should just put curly braces in our blocks.

Instead of writing:

if (foo) foo++;

or:

if (foo) {
  baz();
} else qux();

We write:

if (foo) {
  foo++;
}

or:

if (foo) {
  baz();
} else {
  qux();
}

Then we won’t be confused with where a block starts or ends.

Default Case in Switch Statements

We should put a default case in switch statements.

This way, we handle cases that we don’t expect.

For instance, instead of writing:

switch (foo) {
  case 1:
    doWork();
    break;

  case 2:
    doWork();
    break;
}

We write:

switch (foo) {
  case 1:
    doWork();
    break;

  case 2:
    doWork();
    break;

  default:
    // do nothing
}

Default Parameters to be Last

If we put default parameters to be last, then we know that default parameters are always last.

This makes discerning arguments easier.

For instance, instead of writing:

function createUser(isMember = false, id) {}

We can write:

function createUser(id, isMember = false) {}

Now we don’t have any confusion.

Enforce Newline Before and After Dot

Newline usually comes before the dot.

For instance, we can write:

const a = universe
  .earth;

instead of:

const a = universe.
  earth;

It’s conventional to write it the first way and it’s less confusing.

Dot Notation

Dot notation should be used for most property access code.

Brackets notation should be used for variable properties or properties that aren’t valid identifiers.

For instance, we write:

const x = foo.bar;

instead of:

const x = foo["bar"];

But we can write:

const x = foo[bar];

or:

const x = foo['bar baz'];

Identifiers can’t have spaces so we can use bracket notation to access those.

Newline at the End of Files

Newlines at the end of the file make concatenating files together correctly easier, so we should have them.

For instance, instead of writing:

function work() {
  var foo = 2;
}

We write:

function work() {
  var foo = 2;
}


Return Statements can Return Nothing

We don’t have to return something with return .

It can just be used to end execution of a function.

For instance, we can write:

function doWork(condition) {
  if (condition) {
    return;
  } else {
    return true;
  }
}

return can return nothing to end a function earlier.

This reduces nesting since the example above is the same as:

function doWork(condition) {
  if (condition) {
    return;
  }
  return true;
}

Require === and !==

=== and !== should be used for comparison.

They compare the type and the content and don’t do type coercion.

For instance, instead of writing:

[] == false

we write:

[] === false

And instead of writing:

'hello' != 'world'

We write:

'hello' !== 'world'

“for” Loop Update Clause Moving the Counter in the Right Direction

We should make sure that the loop counter moves in the right direction so that we don’t get an infinite loop.

For instance, instead of writing:

for (var i = 0; i < 10; i--) {
}

We write:

for (var i = 0; i < 10; i++) {
}

Conclusion

We should make sure we don’t make infinite loops accidentally.

Also, we should make sure that super and this should be used properly in classes.

A default case is also useful for switch statements.

Categories
JavaScript Best Practices

JavaScript Best Practices — Classes and Types

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.

Type Checking

We can write consistent APIs to avoid type checking.

Instead of writing:

function travelToNewYork(vehicle) {
  if (vehicle instanceof Airplane) {
    vehicle.fly(this.currentLocation, 'new york');
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, 'new york');
  }
}

We write:

function travelToNewYork(vehicle) {
  vehicle.move(this.currentLocation, 'new york');
}

We have one move method instead of fly and drive methods.

Use Method Chaining

We can return this in our methods to make the charitable.

For instance, instead of writing:

class Cube {
  constructor(length, width, height) {
    this.length = length;
    this.width = width;
    this.height = height;
  }

  setHeight(height) {
    this.height = height;
  }

  setLength(length) {
    this.length = length;
  }

  setWidth(width) {
    this.width = width;
  }

  save() {
    console.log(this.height, this.length, this.width);
  }
}

We write:

class Cube {
  constructor(length, width, height) {
    this.length = length;
    this.width = width;
    this.height = height;
  }

  setHeight(height) {
    this.height = height;
    return this
  }

  setLength(length) {
    this.length = length;
    return this;
  }

  setWidth(width) {
    this.width = width;
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    return this;
  }
}

Then we can use it by writing:

new Cube(1, 2, 3).setWidth(2).setLength(4).setHeight(6);

Composition Over Inheritance

We should compose classes over inheriting them.

Subclasses aren’t as flexible as composing classes.

For instance, instead of writing:

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

  // ...
}

class EmployeePayrollData extends Employee {
  constructor(salary) {
    super();
    this.salary = salary;
  }

  // ...
}

EmployeePayrollData don’t need the content of Employee , so we shouldn’t write this.

Instead, we can write:

class EmployeePayrollData {
  constructor(salary) {
    super();
    this.salary = salary;
  }

  // ...
}

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

  setPayrollData(data){
    this.payrollData = new EmployeePayrollData(data);
  }
}

There’s no is-a relationship between the 2 classes, so there’s no need for extends .

We just use them wherever we like.

Single Responsibility Principle

Every class should have a single responsibility.

For example our Employee class only holds employee data and does employee actions:

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

  //...
}

If we want to add features that aren’t related to employees, then put them somewhere else.

Open/Closed Principle

The Open/Closed Principle means that a piece of code is open for extension and closed for modification.

This means we should be able to add new features without changing the existing code.

Making subclasses is a great way to add functionality:

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this
      .adapter
      .request(url)
      .then(response => {
        // ...
      });
  }
}

class NodeRequester extends HttpRequester{
  constructor() {
    super();
  }

  request(url) {
    // do node request
  }
}

class AjaxRequester extends HttpRequester {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }

  request(url) {
    // do ajax request
  }
}

Liskov Substitution Principle

Liskov Substitution Principle states that we should be able to substitute a parent class with a subclass and get the same result.

This means that we keep shared code in the parent class.

For instance, we write:

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

We have the Rectangle class which has the same methods as the Square class.

They share the same computations.

The only difference is that the width and height are the same in a square.

We can substitute a Rectangle with a Square instance and still set the width and height and get the area.

Conclusion

We should make our APIs consistent to avoid type checking.

We can return this in class methods to make them chainable.

Classes should have a single responsibility and subclasses should be able to be used as replacements of parent classes.

Categories
JavaScript Best Practices

JavaScript Best Practices — Assignments and Imports, Labels, and Regex

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 Assigning to Imported Bindings

We shouldn’t assign anything directly to imported bindings.

It’ll cause run time errors if we do since they’re read-only.

For instance, we shouldn’t write:

import mod, { named } from "./foo"
mod = 1;
named = 2;

Instead, we write:

import mod, { named } from "./foo"
const bar = 1;
const baz = 2;

No Inline Comments After Code

Inline comments after a line of code is a pain to write.

There also might be enough spaces to write them after a line.

For instance, if we have:

function getRandomNumber() {
  return 4; // roll a dice
            // It's a real dice.
}

We’ve to add lots of spaces in the 2nd line of the comment to make them align.

We can make our lives easier if we don’t make them inline:

function getRandomNumber() {
  return 4;
  // roll a dice
  // It's a real dice.
}

No Variable or Function Declarations in Nested Blocks

We shouldn’t have variables or function declarations in nested blocks.

It’s illegal syntax but it’s still allowed by JavaScript interpreters.

For instance, instead of writing:

if (foo) {
  function doSomething() {}
}

We write:

function doSomething() {}

No Invalid Regular Expression strings in RegExp Constructors

If we write a regex, then we should make sure we have a valid pattern inside.

For instance, we shouldn’t write:

RegExp('[')

RegExp('.', 'z')

Instead, we write:

RegExp('foo')

RegExp('d', 'g')

No this Keywords Outside of Classes or Class-Like Objects

We shouldn’t have the this keyword outside of classes or class-like objects.

They can only be used in those entities.

So instead of writing:

this.a = 0;
baz(() => this);

We write:

function Foo() {
  this.a = 0;
  baz(() => this);
}

class Bar {
  constructor() {
    this.a = 0;
    baz(() => this);
  }
}

Foo is a constructor function and Bar is a class, so we can use this in there.

No Irregular Whitespace

We should make sure our whitespace characters are normal whitespace characters.

Most text editors can replace irregular ones with regular ones.

No Iterator

The __iterator__ property isn’t a standard property.

So we shouldn’t use it to create an iterator.

Instead, we use generators to create iterators:

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

We have the generator generator function.

Which we can create a generator from by writing:

const gen = generator();

No Labels That Are Variables Names

We shouldn’t have labels that are the same variable names.

This will cause confusion for most people.

For instance, we shouldn’t have code like:

let x = 'abc';

function bar() {
  x: for (;;) {
    break x;
  }
}

We have x as a variable and a label for a loop.

Instead, we write:

let x = 'abc';

function bar() {
  z: for (;;) {
    break z;
  }
}

Or we just don’t use labels.

No Labeled Statements

Labeled statements lets us set a name for a loop so we can use break or continue with it.

However, it’s a rarely used feature and most people don’t know about it.

Therefore, we should just use something else.

For instance, instead of writing:

outer:
  while (true) {
    while (true) {
      break outer;
    }
  }

We write:

while (true) {
  break;
}

No Unnecessary Nested Blocks

We shouldn’t have unnecessary nested blocks.

For instance, the following isn’t useful:

{
  var foo = baz();
}

It’s only useful for let and const variables, which are block-scoped.

So we write:

{
  let foo = baz();
}

{
  const foo = baz();
}

to keep foo in their own blocks only.

No if Statements as the Only Statement in else Blocks

We shouldn’t have code like:

if (foo) {
  // ...
} else {
  if (bar) {
    // ...
  }
}

Because this is the same as:

if (foo) {
  // ...
} else if (bar) {
    // ...
}

It’s much shorter and it does the same thing.

Conclusion

We can’t reassign values to imported bindings.

Also, we should remove labels, redundant blocks, and redundant if statements.

We also should make sure that regex has valid patterns in it.