Syntax

  • Use let and const over var. There's a lot of discussion on all of this, but we've chosen to stick with const and let.
  • Prefer const for variables that do not or should not be reassigned. For all else, use let.
  • Prefer template literal backticks over single or double quotes for reasons Wes Bos explained.
  • Use consistent capitalization. Capitalization can tell you a lot about your variables, functions, etc. These rules are subjective, so can choose whatever makes sense, but the point is, no matter what is choosen, be consistent.
  • Prefer async/await over Promises and over callbacks. Avoid try/catch blocks as well.
  • camelCase variables and function names wherever possible. In the case of constants, you may choose to upper snake case, e.g const A_LONG_CONSTANT_VARIABLE = 'foo';
/* Bad */
import { get } from 'request-promise';
async function get_article() {
    try {
        const articleBody = await get(`https://en.wikipedia.org/wiki/Main_Page`);
        return JSON.parse(articleBody);
    } catch (err) {
        console.error(err);
    }
}

/* Good */
import { p } from '@theholocron/beebee';
import { get } from 'request-promise';
async function getArticle() {
    const [ error, body ] = await p.call(get(`https://en.wikipedia.org/wiki/Main_Page`));
    if (error) {
      console.error(error);
      return;
    }
    return JSON.parse(body);
}

Variables

  • Use meaningful and pronounceable variable names.
  • Use the same vocabulary for the same type of variable.
  • Use searchable names. You will read more code than you write so it's important that the code written is readable and searchable. By not naming variables that end up being meaningful for understanding our program, we hurt our readers. Make your names searchable.
  • Use explanatory variables.
  • Avoid mental mapping. Explicit is better than implicit.
  • Don't add unneeded context. If your class or object name tells you something, don't repeat that in your variable name.
  • Follow the DOMs API by prefixing getters with get and setters with set.
/* Bad */
const yyyymmdstr = moment().format(`YYYY/MM/DD`);
getUserInfo();
getCustomerRecord();
setTimeout(blastOff, 86400000);
const Car = {
    carMake: `Honda`,
    carColor: `Blue`
};

/* Good */
const currentDate = moment().format(`YYYY/MM/DD`);
getUser();
const MILLISECONDS_IN_A_DAY = 86400000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
const Car = {
    make: `Honda`,
    model: `Accord`,
    color: `Blue`
};

Functions

  • Limiting the amount of function parameters is incredibly important because it makes testing your function easier. Having more than three leads to a combinatorial explosion where you have to test tons of different cases with each separate argument.
  • Use destructuring syntax to make it obvious what properties the function expects. This clones the specified primitive values of the argument object passed into the function and helps prevent side effects. Note: objects and arrays that are destructured from the argument object are NOT cloned. Linters can warn you about unused properties, which would be impossible without destructuring.
  • This is by far the most important rule in software engineering. When functions do more than one thing, they are harder to compose, test, and reason about. When you can isolate a function to just one action, it can be refactored easily and your code will read much cleaner. If you take nothing else away from this guide other than this, you'll be ahead of many developers.
  • Naming things can be hard, but the best advice is to name things for what they do or return
  • Functions should only be one level of abstraction. When you have more than one level of abstraction your function is usually doing too much. Splitting up functions leads to reusability and easier testing.
/* Bad */
function createMenu(title, body, buttonText, cancellable) { ... }
createMenu(`Foo`, `Bar`, `Baz`, true);

/* Good */
function createMenu({ title, body, buttonText, cancellable }) { ...  }
createMenu({
    title: `Foo`,
    body: `Bar`,
    buttonText: `Baz`,
    cancellable: true
});

/* Bad */
function addToDate(date, month) { ... }
// It's hard to tell from the function name what is added
addToDate(new Date(), 1);

/* Good */
function addMonthToDate(month, date) { ... }
addMonthToDate(1, new Date());

Classes

Prefer ES2015/ES6 classes over ES5 plain functions

It's very difficult to get readable class inheritance, construction, and method definitions for classical ES5 classes. If you need inheritance (and be aware that you might not), then prefer ES2015/ES6 classes. However, prefer small functions over classes until you find yourself needing larger and more complex objects.

Use method chaining

This pattern is very useful in JavaScript and you see it in many libraries such as jQuery and Lodash. It allows your code to be expressive, and less verbose. For that reason, use method chaining and take a look at how clean your code will be. In your class functions, simply return this at the end of every function, and you can chain further class methods onto it.

/* Without chaining */
class Car {
    constructor(make, model, color) {
        this.make = make;
        this.model = model;
        this.color = color;
    }
    setMake(make) {
        this.make = make;
    }
    setModel(model) {
        this.model = model;
    }
    setColor(color) {
        this.color = color;
    }
    save() {
        console.log(this.make, this.model, this.color);
    }
}

const car = new Car(`Ford`, `F-150`, `red`);
car.setColor(`pink`);
car.save();

/* With chaining */
class Car {
    constructor(make, model, color) {
        this.make = make;
        this.model = model;
        this.color = color;
    }
    setMake(make) {
        this.make = make;
        // NOTE: Returning this for chaining
        return this;
    }
    setModel(model) {
        this.model = model;
        return this;
    }
    setColor(color) {
        this.color = color;
        return this;
    }
    save() {
        console.log(this.make, this.model, this.color);
        return this;
    }
}

const car = new Car(`Ford`, `F-150`, `red`).setColor(`pink`).save();

Error Handling

Thrown errors are a good thing! They mean the runtime has successfully identified when something in your program has gone wrong and it's letting you know by stopping function execution on the current stack, killing the process (in Node), and notifying you in the console with a stack trace.

Do not ignore caught errors. Doing nothing with a caught error doesn't give you the ability to ever fix or react to said error. Logging the error to the console (console.log) isn't much better as often times it can get lost in a sea of things printed to the console. If you wrap any bit of code in a try/catch it means you think an error may occur there and therefore you should have a plan, or create a code path, for when it occurs.

For the same reason you shouldn't ignore caught errors from try/catch.

/* Bad */
try {
    functionThatMightThrow();
} catch (e) { }

/* Good */
try {
  functionThatMightThrow();
} catch (error) {
    // One option (more noisy than console.log):
    console.error(error);
    // Another option:
    notifyUserOfError(error);
    // Another option:
    reportErrorToService(error);
    // OR do all three!
}

/* Bad */
getData()
    .then(data => {
        functionThatMightThrow(data);
    })
    .catch(error => {
        console.log(error);
    });

/* Good */
getData()
    .then(data => {
        functionThatMightThrow(data);
    })
    .catch(error => {
        // One option (more noisy than console.log):
        console.error(error);
        // Another option:
        notifyUserOfError(error);
        // Another option:
        reportErrorToService(error);
        // OR do all three!
    });

Comments

Code is written and maintained by people. Ensure your code is descriptive, well commented, and approachable by others. Great code comments convey context or purpose. Do not simply reiterate a component or class name.

Be sure to write in complete sentences for larger comments and succinct phrases for general notes. And avoid journal style comments in favor of better commit messages and git log for history.

Comments are not meant to replace version control, so don't comment out code or mark to do later; remove it and create a commit in history.

/* Bad */
/**
 * 2016-12-20: Removed monads, didn't understand them (RM)
 * 2016-10-01: Improved using special monads (JP)
 * 2016-02-03: Removed type-checking (LI)
 * 2015-03-14: Added combine with type-checking (JR)
 */
function combine(a, b) {
    return a + b;
}

/* Good */
/* Meant to combine numbers.
 * Allows strings in the event they contain numbers like 'one'
 */
function combine(a, b) {
    return a + b;
}

Organization

  • We tend to read code from top-to-bottom, like a newspaper. Because of this, make your code read that way. e.g. if a function calls another, keep those functions vertically close in the source file. Ideally, keep the caller right above the callee.
  • Organize sections of code by component.
  • Develop a consistent commenting hierarchy.
  • Use consistent white space to your advantage when separating sections of code for scanning larger documents.
/* Bad */
class PerformanceReview {
    constructor(employee) {
        this.employee = employee;
    }
    getPeerReviews() { ... }
    perfReview() {
        this.getPeerReviews();
        this.getSelfReview();
    }
    getSelfReview() { ... }
}

/* Good */
class PerformanceReview {
    constructor(employee) {
        this.employee = employee;
    }
    perfReview() {
        this.getPeerReviews();
        this.getSelfReview();
    }
    getPeerReviews() { ... }
    getSelfReview() { ... }
}