Categories
JavaScript Best Practices

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

Spread the love

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.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *