Skip to content
Emmett Nicholas edited this page Aug 17, 2015 · 29 revisions

Airtable JavaScript Style Guide

Whitespace

  • 4 space indentation.
  • Spaces, not literal tabs.
  • No trailing whitespace.
  • Every file should end with a newline.

Comments

// Use this style for inline comments. Notice the space character after "//".
// This style works fine for multi-line comments.

/**
 * Use this alternate style for documenting methods and classes. This comment
 * style should only appear directly above method/class definitions.
 */

that = this

Use the variable that to maintain a reference to this when necessary.

var that = this;

Braces

Use this style:

function() {
    if (a < 0) {
        console.log("a is negative");
        handleNegativeA();
    } else if (a > 0) {
        console.log("a is positive");
        handlePositiveA();
    } else {
        console.log("a is zero or NaN");
        handleOtherA();
    }
}

Braces are never optional, even around single-statement blocks.

// Good:
if (!alreadyDone) {
    doSomething();
}

// Bad:
if (!alreadyDone)
    doSomething();

An alternate, condensed style is allowed only when calling the done or next callback with an error:

if (err) { done(err); return; }
// or
if (err) { next(err); return; }

If the code deviates even slightly from the above, then condensing the code into a single line is not allowed.

Variable declaration

We're less strict than others when it comes to declaring variables. For example, it's not important that variables be declared at the top of the function. However, it's important to keep a couple rules in mind:

A variable should only be declared once per function.

// Good:
var count;
if (user) {
    count = user.count;
} else {
    count = 0;
}

// Bad:
if (user) {
    var count = user.count;
} else {
    var count = 0;
}

Never use JavaScript's condensed syntax for declaring multiple variables.

// Good:
var foo;
var bar;

// Bad:
var foo, bar;

Spaces around tokens

  • a = b + 1, not a=b+1
  • function(a, b, c) {, not function(a,b,c) {
  • if (a), not if(a)

Note This is admittedly inconsistent, but we happen to use a denser notation for object literals than most others:

  • {a: 1, b: 2}, not { a: 1, b: 2 }

Arguments

If the function has more than a few arguments and it's part of the class's public interface, than assert that the arguments are of the expected type. This boilerplate can be tedious, but we've found that it pays dividends in the long run. (If we ever switch to TypeScript, we can do away with it.)

function doSomething(userId, tableId, rowId, count, done) {
    hs.assert(hs.utils.isValidUserId(userId));
    hs.assert(hs.utils.isValidTableId(tableId));
    hs.assert(hs.utils.isValidRowId(rowId));
    hs.assert(hs.utils.isNonNegativeInt(count));
    hs.assert(_.isFunction(done));

    // stuff...
}

If the function has more than ~4 or 5 arguments, then including them as named properties of the opts argument is recommended:

function doSomething(userId, opts, done) {
    if (arguments.length === 2) {
        done = opts;
        opts = {};
    }
    hs.assert(hs.utils.isValidUserId(userId));
    hs.assert(_.isFunction(done));

    opts = hyperUtils.setAndEnforceDefaultOpts({
        tableId: null
        rowId: null,
        count: 0
    }, opts);

    hs.assert(hs.utils.isValidTableId(opts.tableId));
    hs.assert(hs.utils.isValidRowId(opts.rowId));
    hs.assert(hs.utils.isNonNegativeInt(opts.count));

    // stuff...
}

Async

  • Use the async lib for async control flow. hs.async is Airtable's wrapper around that library. We avoid other control flow options, e.g. promises, for consistency's sake.
  • An async function's final callback arg should be named done, and should always be the last argument in the function.
  • Inner callback functions should be named next (followed by innerNext if absolute necessary)
function getUser(userId, done) {
    hs.async.waterfall([
        function(next) {
            doSomething(next);
        },
        function(next) {
            doSomethingElse(next);
        }
    ], done);
}

Classes

  • Use the Class.mod module to facilitate creation of classes, as well as inheritance. It is a slightly modified version of John Resig's code: http://ejohn.org/blog/simple-javascript-inheritance/
  • Class names should be capitalized, and instance names should not be.
  • Private or protected variables and methods in a class should be prefixed with "_".
  • To create singletons, use the normal class pattern, and then instantiate the object at the end of the file.
var Foo = Class.extend({
    foo: function() {
        // public method
    },
    _bar: function() {
        // private method
    }
});

// Export the class
module.exports = Foo;
// OR export the singleton
module.exoprts = new Foo();

Errors

  • Never throw errors synchronously.
  • On the server side, use hs.utils.spawnError to generate errors, not the native Error constructor.
  • Every async error must be handled; no error can be ignored.

Equality

  • Always use === and !==; never use == or !=.

Underscore

Underscore is really handy, and we use it for almost all array/object manipulation. In particular, it should be used for all iteration.

// Good:
_.each(usersById, function(user, userId) {
    doSomething(user); 
});

// Bad:
var userId;
var user;
for (userId in usersById) {
    if (usersById.hasOwnProperty(userId) {
        user = usersById[userId];
        doSomething(user);
    }
}

In certain performance-critical code paths, we've begun to phase out Underscore in favor of lodah. It's a bit weird to use both libraries, so at some point we should probably switch to lodash completely.

camelCase

  • Use camelCase for all identifiers, e.g. function and variable names.
  • All git branch names should also be camelCase.

Readability

Optimize for readability over terseness in all cases. Long and descriptive variable/method names are strongly preferred!

Don't use || or && for control flow:

// Good:
if (!alreadyDone) {
    doSomething();
}
// Bad:
alreadyDone || doSomething();

// It's OK to use `||` for assigning default values though:
var count = user.count || 0;

Separate tail cals into two lines so it's easier to step through with the debugger:

// Good:
function foo() {
    var ret = bar();
    return ret;
}

// Bad:
function foo() {
    return bar();
}