Free email newsletter: “ES.next News

2013-07-30

A meta style guide for JavaScript

JavaScript has many great style guides. Thus, there is no need to write yet another one. Instead, this blog post describes commonly accepted meta style rules and conventions that I like that are controversial. The idea is to help you make a more informed decision about what is right for you.

Existing style guides

The two style guides I like best are: Additionally, there are two style guides that go meta:

General 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

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.

Shorter isn’t always better. Sometimes writing more means that things are actually faster to read. 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 with 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 sub-functions. 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. One does 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 code”, it’s code where most of the effort went into making everything easy to understand. Note that “other people” includes your past 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 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 agrees on the following best practices:
  • Use strict mode [1]. It prevents several problems.
  • Always use semicolons. Avoid the pitfalls of automatic semicolon insertion [2].
  • Always use strict equality (===) and strict inequality (!==). I recommend to always obey this rule [3]. 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
    
  • Always use braces (if statements and loops). If an if statement doesn’t have an else clause and can be written in a single line, I omit the braces.
  • Use the One True Bace Style [4] (the opening brace starts in the same line as the statement it belongs to).
  • Indentation: either use only spaces or only tabs for indentation, but don’t mix them.
  • Quoting strings [5]: You can write string literals with either single quotes or double quotes in JavaScript. Single quotes are slightly more common. They make it easier to work with HTML code (which normally has attribute values in double quotes). On the other hand, several other languages (C, Java, etc.) only have double-quoted strings (meaning that they look more familiar in JavaScript code). Furthermore, with the JSON format, you don’t have a choice, you have to double-quote strings.

Prefer literals to constructors

Several literals produce objects that can also be created by constructors. However, the latter is normally the better choice:
    var obj = new Object(); // no
    var obj = {}; // yes

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

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

    var regex = new RegExp('abc'); // avoid if possible
    var regex = /abc/; // 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 (if the operand comes first, its value is returned before incrementing/decrementing it), you still need to think to figure out what is going on:

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

    // Easy to understand
    ++foo;
    return foo;
Miscellanous. Various other kinds of cleverness:
    if (x === void 0) x = 0; // not that important, any more, thanks to ES5
    if (x === undefined) x = 0; // preferable

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

Acceptable cleverness

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

Parameter default values. Using the Or (||) operator to assign default values to parameters is a common practice:

    function f(x) {
        x = x || 0; // ok
        if (x === undefined) x = 0; // ok
        if (!x) x = 0; // ok
        ...
    }
Generic methods. If you use methods generically, you can abbreviate Object.prototype as {} and Array.prototype as []. 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.
    Object.prototype.hasOwnProperty.call(obj, propName)  // ok
    {}.hasOwnProperty.call(obj, propName)  // ok

    Array.prototype.slice.call(arguments) // ok
    [].slice.call(arguments) // ok
ECMAScript 5 – trailing commas. Trailing commas in object literals are legal in ECMAScript 5 [7]:
    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 names:
    > var obj = { new: 'abc' };
    > obj.new
    'abc'

Controversial rules

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

Syntax

Tight whitespace. I like relatively tight whitespace. The role model is written English: There are no spaces after opening parentheses and before closing parentheses. And there are spaces after commas.
    var result = foo('a', 'b');
    var arr = [ 1, 2, 3 ];
    if (flag) {
        ...
    }
For anonymous functions, I follow Crockford’s rule of having a space after the keyword function. The rationale is this is what a named function expression looks like if you remove the name.
    function foo() { ... }  // named function expression
    function () { ... }     // 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 4 spaces per level of indentation, because that makes the indentation more visible.

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;
Advantages [8]: 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 w.r.t. hoisting and pretend that var declarations are block-scoped. That is, you can declare a variable in the context in which it is used (inside a loop [8], inside a then-block or an else-block, etc.). This kind of local encapsulation makes a code fragment easier to understand in isolation. Is is also easier to remove the code fragment or to move it somewhere else.

Put expressions with operators in parentheses. This helps with reading, because it is easier to make out the scopes of the operators. Two examples:

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

    return foo === bar;  // no
    return (foo === bar);  // yes

Object-orientation

Prefer constructors over other instance creation patterns. I recommend [9] to:
  • 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.
  • Speed advantages. In modern engines, using instances of constructors is very fast (e.g. via hidden classes).
  • Classes, the default inheritance construct in ECMAScript 6 (see below), 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. That means that constructors abstract how instances are created – you can always change your mind and, for example, return the instance of a sub-constructor instead of setting up an instance of the current constructor.

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 [10]. 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).

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

Coercing. Coerce a value to a type via Boolean, Number, String(), Object() (used as functions – never use those functions as constructors). Rationale: more descriptive.
    > +'123'  // no
    123
    > Number('123')  // yes
    123

    > ''+true  // no
    'true'
    > String(true)  // yes
    'true'

Avoid this as an implicit parameter. this should only refer to the receiver of the current method invocation, it should not be abused as an implicit parameter. Rationale: such functions are easier to call and you can later switch to ECMAScript 6’s arrow functions [11]. More abstractly, I 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. 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')) ... // ok
    if (Object.prototype.hasOwnProperty.call(obj, 'foo')) ... // safer

Abbreviate Object.prototype with {} and Array.prototype with [].

    Object.prototype.hasOwnProperty.call(obj, 'foo')
    {}.hasOwnProperty.call(obj, 'foo')

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

Fail fast. If you can, it’s best to fail fast and to not fail silently. JavaScript is only so forgiving (division by zero etc.), because the first version of ECMAScript did not have exceptions. For example: don’t coerce values, throw an exception. However, you have to find a way 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.

References

  1. JavaScript’s strict mode: a summary
  2. Automatic semicolon insertion in JavaScript
  3. When is it OK to use == in JavaScript?
  4. Brace styles and JavaScript
  5. JavaScript: single quotes or double quotes?
  6. Arrays in JavaScript [explains that the array constructor is only safe if you call it with a single argument – a non-negative integer]
  7. Trailing commas in object literals and array literals
  8. Variable declarations: three rules you can break
  9. In defense of JavaScript’s constructors
  10. Private data for objects in JavaScript
  11. ECMAScript.next: arrow functions and method definitions

No comments: