Chapter 26. A Meta Code Style Guide

JavaScript has many great style guides. Thus, there is no need to write yet another one. Instead, this chapter describes meta style rules and surveys existing style guides and established best practices. It also mentions practices I like that are more controversial. The idea is to complement existing style guides rather than to replace them.

Existing Style Guides

These are style guides that I like:

Additionally, there are two style guides that go meta:

General Tips

This section will cover some general code writing tips.

Code Should Be Consistent

There are two important rules for writing consistent code. The first rule is that, if you start a new project, you should come up with a style, document it, and follow it everywhere. The larger the team, the more important it is to check for adherence to the style automatically, via tools such as JSHint. When it comes to style, there are many decisions to make. Most of them have generally agreed-upon answers. Others have to be defined per project. For example:

  • How much whitespace (after parentheses, between statements, etc.)
  • Indentation (e.g., how many spaces per level of indentation)
  • How and where to write var statements

The second rule is that, if you are joining an existing project, you should follow its rules rigorously (even if you don’t agree with them).

Code Should Be Easy to Understand

Everyone knows that debugging is twice as hard as writing a program in the first place. So if you are as clever as you can be when you write it, how will you ever debug it? —Brian Kernighan

For most code, the time used for reading it is much greater than the time used for writing it. It is thus important to make the former as easy as possible. Here are some guidelines for doing that:

Shorter isn’t always better
Sometimes writing more means that things are actually faster to read. Let’s consider two examples. First, familiar things are easier to understand. That can mean that using familiar, slightly more verbose, constructs can be preferable. Second, humans read tokens, not characters. Therefore, redBalloon is easier to read than rdBlln.
Good code is like a textbook

Most code bases are filled with new ideas and concepts. That means that if you want to work with a code base, you need to learn those ideas and concepts. In contrast to textbooks, the added challenge with code is that people will not read it linearly. They will jump in anywhere and should be able to roughly understand what is going on. Three parts of a code base help:

  • Code should explain what is happening; it should be self-explanatory. To write such code, use descriptive identifiers and break up long functions (or methods) into smaller subfunctions. If those functions are small enough and have meaningful names, you can often avoid comments.
  • Comments should explain why things are happening. If you need to know a concept to understand the code, you can either include the name of the concept in an identifier or mention it in a comment. Someone reading the code can then turn to the documentation to find out more about the concept.
  • Documentation should fill in the blanks left by the code and the comments. It should tell you how to get started with the code base and provide you with the big picture. It should also contain a glossary for all important concepts.
Don’t be clever; don’t make me think
There is a lot of clever code out there that uses in-depth knowledge of the language to achieve impressive terseness. Such code is usually like a puzzle and difficult to figure out. You will encounter the opinion that if people don’t understand such code, maybe they should really learn JavaScript first. But that’s not what this is about. No matter how clever you are, entering other people’s mental universes is always challenging. So simple code is not stupid, it’s code where most of the effort went into making everything easy to understand. Note that “other people” includes your future selves. I often find that clever thoughts I had in the past don’t make sense to my present self.
Avoid optimizing for speed or code size

Much cleverness is directed at these optimizations. However, you normally don’t need them. On one hand, JavaScript engines are becoming increasingly smart and automatically optimize the speed of code that follows established patterns. On the other hand, minification tools (Chapter 32) rewrite your code so that it is as small as possible. In both cases, tools are clever for you, so that you don’t have to be.

Sometimes you have no choice but to optimize the performance of your code. If you do, be sure to measure and optimize the right pieces. In browsers, the problems are often related to DOM and HTML and not the language proper.

Commonly Accepted Best Practices

A majority of JavaScript programmers agree on the following best practices:

  • Use strict mode. It makes JavaScript a cleaner language (see Strict Mode).
  • Always use semicolons. Avoid the pitfalls of automatic semicolon insertion (see Automatic Semicolon Insertion).
  • Always use strict equality (===) and strict inequality (!==). I recommend never deviating from this rule. I even prefer the first of the following two conditions, even though they are equivalent:

    if (x !== undefined && x !== null) ...  // my choice
    if (x != null) ...  // equivalent
  • Either use only spaces or only tabs for indentation, but don’t mix them.
  • Quoting strings: You can write string literals with either single quotes or double quotes in JavaScript. Single quotes are more common. They make it easier to work with HTML code (which normally has attribute values in double quotes). Other considerations are mentioned in String Literals.
  • Avoid global variables (Best Practice: Avoid Creating Global Variables).

Brace Styles

In languages where braces delimit blocks of code, a brace style determines where you put those braces. Two brace styles are most common in C-like languages (such as Java and JavaScript): Allman style and 1TBS.

Allman style

If a statement contains a block, that block is considered as somewhat separate from the head of the statement: its opening brace is in a line of its own, at the same indentation level as the head. For example:

// Allman brace style
function foo(x, y, z)
{
    if (x)
    {
        a();
    }
    else
    {
        b();
        c();
    }
}

1TBS (One True Brace Style)

Here, a block is more closely associated with the header of its statement; it starts after it, in the same line. The bodies of control flow statements are always put in braces, even if there is only a single statement. For example:

// One True Brace Style
function foo(x, y, z) {
    if (x) {
        a();
    } else {
        b();
        c();
    }
}

1TBS is a variant of the (older) K&R (Kernighan and Ritchie) style.[21] In K&R style, functions are written in Allman style and braces are omitted where they are not necessary—for example, around single-statement then cases:

// K&R brace style
function foo(x, y, z)
{
    if (x)
        a();
    else {
        b();
        c();
    }
}

JavaScript

The de facto standard in the JavaScript world is 1TBS. It has been inherited from Java and most style guides recommend it. One reason for that is objective. If you return an object literal, you must put the opening brace in the same line as the keyword return, like this (otherwise, automatic semicolon insertion inserts a semicolon after return, meaning that nothing is returned; see Pitfall: ASI can unexpectedly break up statements):

return {
    name: 'Jane'
};

Obviously, an object literal is not a code block, but things look more consistent and you are less likely to make mistakes if both are formatted the same way.

My personal style and preference is:

  • 1TBS (which implies that you use braces whenever possible).
  • As an exception, I omit braces if a statement can be written in a single line. For example:

    if (x) return x;

Prefer Literals to Constructors

Several constructors produce objects that can also be created by literals. The latter is normally the better choice:

var obj = new Object(); // no
var obj = {}; // yes

var arr = new Array(); // no
var arr = []; // yes

var regex = new RegExp('abc'); // avoid if possible
var regex = /abc/; // yes

Don’t ever use the constructor Array to create an array with given elements. Initializing an array with elements (avoid!) explains why:

var arr = new Array('a', 'b', 'c'); // never ever
var arr = [ 'a', 'b', 'c' ]; // yes

Don’t Be Clever

This section collects examples of unrecommended cleverness.

Conditional operator

Don’t nest the conditional operator:

// Don’t:
return x === 0 ? 'red' : x === 1 ? 'green' : 'blue';

// Better:
if (x === 0) {
    return 'red';
} else if (x === 1) {
    return 'green';
} else {
    return 'blue';
}

// Best:
switch (x) {
    case 0:
        return 'red';
    case 1:
        return 'green';
    default:
        return 'blue';
}

Abbreviating if statements

Don’t abbreviate if statements via logical operators:

foo && bar(); // no
if (foo) bar(); // yes

foo || bar(); // no
if (!foo) bar(); // yes

Increment operator

If possible, use the increment operator (++) and the decrement operator (--) as statements; don’t use them as expressions. In the latter case, they return a value and while there is a mnemonic, you still need to think to figure out what is going on:

// Unsure: what is happening?
return ++foo;

// Easy to understand
++foo;
return foo;

Checking for undefined

if (x === void 0) x = 0; // not necessary in ES5
if (x === undefined) x = 0; // preferable

Starting with ECMAScript 5, the second way of checking is better. Changing undefined explains why.

Converting a number to an integer

return x >> 0; // no
return Math.round(x); // yes

The shift operator can be used to convert a number to an integer. However, it is usually better to use a more explicit alternative such as Math.round(). Converting to Integer gives an overview of all ways of converting to integer.

Acceptable Cleverness

Sometimes you can be clever in JavaScript—if the cleverness has become an established pattern.

Default values

Using the Or (||) operator to provide default values is a common pattern—for example, for parameters:

function f(x) {
    x = x || 0;
    ...
}

For details and more examples, consult Pattern: providing a default value.

Generic methods

If you use methods generically, you can abbreviate Object.prototype as {}. The following two expressions are equivalent:

Object.prototype.hasOwnProperty.call(obj, propKey)
{}.hasOwnProperty.call(obj, propKey)

And Array.prototype can be abbreviated as []:

Array.prototype.slice.call(arguments)
[].slice.call(arguments)

I’m ambivalent about this one. It is a hack (you are accessing a prototype property via an instance). But it reduces clutter, and I expect engines to eventually optimize this pattern.

ECMAScript 5: trailing commas

Trailing commas in object literals are legal in ECMAScript 5:

var obj = {
    first: 'Jane',
    last: 'Doe', // legal: trailing comma
};

ECMAScript 5: reserved words

ECMAScript 5 also allows you to use reserved words (such as new) as property keys:

> var obj = { new: 'abc' };
> obj.new
'abc'

Controversial Rules

Let’s look at some conventions I like that are a bit more controversial.

Syntax

We’ll start with syntactic conventions:

Tight whitespace

I like relatively tight whitespace. The model is written English: there are no spaces after an opening parenthesis and before a closing parenthesis. And there are spaces after commas:

var result = foo('a', 'b');
var arr = [ 1, 2, 3 ];
if (flag) {
    ...
}

For anonymous functions, I follow Douglas Crockford’s rule of having a space after the keyword function. The rationale is that this is what a named function expression looks like if you remove the name:

function foo(arg) { ... }  // named function expression
function (arg) { ... }     // anonymous function expression
Four spaces per indentation level
Most code I am seeing uses spaces for indentation, because tabs are displayed so differently between applications and operating systems. I prefer four spaces per level of indentation, because that makes the indentation more visible.
Put the conditional operator in parentheses

This helps with reading, because it is easier to make out the scope of the operator:

return result ? result : theDefault;  // no
return (result ? result : theDefault);  // yes

Variables

Next, I’ll cover conventions for variables:

One variable declaration per line

I don’t declare multiple variables with a single declaration:

// no
var foo = 3,
    bar = 2,
    baz;

// yes
var foo = 3;
var bar = 2;
var baz;

The advantages of this approach are that deleting, inserting, and rearranging lines is simpler and the lines are automatically indented correctly.

Keep variable declarations local
If your function isn’t too long (which it shouldn’t be, anyway), then you can afford to be less careful with regard to hoisting and pretend that var declarations are block-scoped. In other words, you can declare a variable in the context in which it is used (inside a loop, inside a then block or an else block, etc.). This kind of local encapsulation makes a code fragment easier to understand in isolation. It is also easier to remove the code fragment or to move it somewhere else.
If you are inside a block, stay inside that block

As an addendum to the previous rule: don’t declare the same variable twice, in two different blocks. For example:

// Don’t do this
if (v) {
    var x = v;
} else {
    var x = 10;
}
doSomethingWith(x);

The preceding code has the same effect and intention as the following code, which is why it should be written that way:

var x;
if (v) {
    x = v;
} else {
    x = 10;
}
doSomethingWith(x);

Object Orientation

Now we’ll cover conventions relating to object orientation.

Prefer constructors over other instance creation patterns

I recommend that you:

  • Always use constructors.
  • Always use new when creating an instance.

The main advantages of doing so are:

  • Your code better fits into the JavaScript mainstream and is more likely to be portable between frameworks.
  • In modern engines, using instances of constructors is very fast (e.g., via hidden classes).
  • Classes, the default inheritance construct in the upcoming ECMAScript 6, will be based on constructors.

For constructors, it is important to use strict mode, because it protects you against forgetting the new operator for instantiation. And you should be aware that you can return any object in a constructor. More tips for using constructors are mentioned in Tips for Implementing Constructors.

Avoid closures for private data
If you want an object’s private data to be completely safe, you have to use closures. Otherwise, you can use normal properties. One common practice is to prefix the names of private properties with underscores. The problem with closures is that code becomes more complicated (unless you put all methods in the instance, which is unidiomatic and slow) and slower (accessing data in closures is currently slower than accessing properties). Keeping Data Private covers this topic in more detail.
Write parens if a constructor has no arguments

I find that such a constructor invocation looks cleaner with parentheses:

var foo = new Foo;  // no
var foo = new Foo();  // yes
Be careful about operator precedence

Use parens so that two operators don’t compete with each other—the result is not always what you might expect:

> false && true || true
true
> false && (true || true)
false
> (false && true) || true
true

instanceof is especially tricky:

> ! {} instanceof Array
false
> (!{}) instanceof Array
false
> !({} instanceof Array)
true

However, I find method calls after a constructor unproblematic:

new Foo().bar().baz();  // ok
(new Foo()).bar().baz();  // not necessary

Miscellaneous

This section collects various tips:

Coercing

Coerce a value to a type via Boolean, Number, String(), Object() (used as functions—never use those functions as constructors). The rationale is that this convention is more descriptive:

> +'123'  // no
123
> Number('123')  // yes
123

> ''+true  // no
'true'
> String(true)  // yes
'true'
Avoid this as an implicit parameter

this should refer only to the receiver of the current method invocation; it should not be abused as an implicit parameter. The rationale is that such functions are easier to call and understand. I also like to keep object-oriented and functional mechanisms separate:

// Avoid:
function handler() {
    this.logError(...);
}

// Prefer:
function handler(context) {
    context.logError(...);
}
Check for the existence of a property via in and hasOwnProperty (see Iteration and Detection of Properties)

This is more self-explanatory and safer than comparing with undefined or checking for truthiness:

// All properties:
if (obj.foo)  // no
if (obj.foo !== undefined)  // no
if ('foo' in obj) ... // yes

// Own properties:
if (obj.hasOwnProperty('foo')) ... // risky for arbitrary objects
if (Object.prototype.hasOwnProperty.call(obj, 'foo')) ... // safe
Fail fast
If you can, it’s best to fail fast and to not fail silently. JavaScript is only so forgiving (e.g., division by zero), because the first version of ECMAScript did not have exceptions. For example, don’t coerce values; throw an exception. However, you have to find ways to recover gracefully from failure when your code is in production.

Conclusion

Whenever you are considering a style question, ask yourself: what makes my code easier to understand? Resist the temptation to be clever and leave most of the mechanical cleverness to JavaScript engines and minifiers (see Chapter 32).



[21] Some people even say that they are synonyms, that 1TBS is a way to jokingly refer to K&R.

Get Speaking JavaScript now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.