Home

Awesome

The GoCardless JavaScript style guide

This style guide is for general JavaScript conventions and styles that we follow across all our JavaScript. For Angular specific conventions, refer to the Angular Style Guide.

Browser Support

The projects we work on support the following browsers:

Be aware of this when reading the guide - some of the JS shown here may not work in older versions.

Strings

Use single quotes for all strings.

Why: in JavaScript there is no difference between single and double quotes. Rather than have a mix throughout a code base, pick one and stick to it.

Keep line length at around 80 characters.

Why: this encourages developers to write lines that do less, and extract variables and functions where a line is longer than required.

We don’t have an 80 char hard limit because sometimes it’s more readable to let a line be 85 characters rather than break it up, but in general you should aim for 80 characters or less.

Use String.prototype.includes to check for substrings

Why: it's intent is much clearer than indexOf, and it is included as part of the ES6 specification

In a JS project that is transpiled using Traceur, Babel or similar, use the new String.prototype.includes method:

// bad
if (str.indexOf('foo') > -1) {
}

// good
if (str.includes('foo')) {
}

Async

Ensure that each promise chain has at least one catch.

Why: ensures that errors are always handled.

// bad
doSomething().then(...)

// good
doSomething().then(...).catch(...)

Prefer multiple then callbacks over one then callback which performs multiple operations.

Why: this keeps promise chains easier to read, and keeps callback functions smaller.

// bad
doSomething().then(function processSomething(data) {
  // do something
  // do something else
  // do something else else
});

// good
doSomething().then(function processSomething(data) {
 // do something
 return data;
}).then(function somethingElse(data) {
 // do something else
 return data;
}).then(function anotherThing(data) {
  // and so on
});

Functions

Name functions that are given as a callback.

Why: the function name replaces “anonymous function” in stack traces, which makes debugging easier. Naming functions also helps code be self documenting.

// bad
createUser().then(function() {
});

// good
createUser().then(function userCreatedSuccesfully() {
});

Prefer function declarations to function expressions.

Why: function declarations are easier to pick out in larger code files.

Ensure that the function is defined above its first usage.

Why: hoisting enables JavaScript files to have their most important content at the top. However, we’re used to reading top to bottom and left to right, so this can be confusing.

// this will work but is unclear
thing();

// more code

function thing() {
}

Prefer guard clauses to nested function bodies.

Why: indenting an entire function body makes it harder to read. Providing a base case early keeps the code cleaner.

// bad
function doSomethingOnOddNumbers(x) {
  if (x % 2 === 1) {
    // do stuff
  }
};

// good
function doSomethingOnOddNumbers(x) {
  if (x % 2 === 0) return;
  // do stuff
}

Leave a space after the closing ) and before the opening {, but never before the opening (.

// bad
function doSomething () {
}

// bad
function doSomething(){
}

// good
function doSomething() {
}

When a function has arguments that make the line longer than ~80 characters, split it into one parameter per line.

Why: keeps lines below 80 characters, after which readability declines.

// good
function SomeController(foo, bar, baz) {
}

// bad
function SomeController(foo, bar, baz, abc, def, ghi, jkl, mno, pqr, stu, vwx, yz) {
}

// good
function SomeController(
  foo,
  bar,
  baz,
  abc,
  def,
  ghi,
  jkl,
  mno,
  pqr,
  stu,
  vwx,
  yz
) {
}

Objects

When an object literal has more than one property, split it into one property per line, leaving a trailing comma.

Why: keeps lines below 80 characters, after which readability declines. Trailing commas make it easier to reorder existing lines without editing them, and makes new lines clearer in the diff.

// good
{ foo: 2 }

// bad
{ foo: 2, bar: 3 }

// bad
{
  foo: 2,
  bar: 3
}

// good
{
  foo: 2,
  bar: 3,
}

Arrays

When using array methods such as forEach, map, etc, pass function names in if possible.

Why: avoids unnecessary function wrapping.

function isOdd(x) {
  return x % 2 === 1;
}

// bad
[1, 2, 3].filter(function(x) {
  return isOdd(x);
});

// good
[1, 2, 3].filter(isOdd);

Never mutate the array or objects within an array in a forEach, map or similar.

Why: JavaScript's object mutation can be very implicit and easy to miss. It's much better to have slightly more verbose but much clearer code.

// bad -  payments array has been mutated, not obvious
payments.forEach(function(payment) {
  payment.id = 'ABC123'
});

// bad - the original payments array is mutated
payments.map(function(payment) {
  payment.id = 'ABC123';
});

// good - don't mutate the array, but create and modify a new one:
payments = _.cloneDeep(payments).map(function(payment) {
  payment.id = 'ABC123';
  return payment;
});

In practice we may not use _.cloneDeep - alter the above code as appropriate based on the libraries available.

Leave a trailing comma in array literals that span multiple lines.

Why: it’s easier to reorder items and the diff is cleaner when a new item is added.

// bad
[
  'jack',
  'max'
]

// good
[
  'jack',
  'max',
]

Prefer LoDash's _.includes method to test for Array inclusion

Why: its intent is much clearer than using indexOf.

If LoDash is available and included in the project, _.includes is much easier to read and cleaner to use than indexOf.

// bad
if (names.indexOf('jack') > -1) {
}

// good
if (_.includes(names, 'jack')) {
}

Variables

Don’t declare a variable within a block.

Why: it’s unclear where the variable is defined.

// bad
if (x) {
  var y = 2;
} else {
  var y = 3;
}

// good
var y;
if (x) {
  y = 2;
} else {
  y = 3;
}

Use ternary operators for very succinct conditionals which change the initial value of a variable.

Why: ternary operators are concise when used with small conditionals.

Use an if statement if any branch of the condition is complex.

var y = x ? 2 : 3;

One variable per line.

Why: adding a new variable does not mean other lines need to be edited, and existing lines can be reordered easily.

// bad
var x, y, z;

// good
var x;
var y;
var z;

Conditionals

Always prefer === and !== when comparing.

Why: eliminates errors caused by JavaScript’s complex equality and coercion rules.

Use == or != only when checking a variable is undefined or null:

Why: using == checks for both undefined and null, whereas === only checks for one.

// bad
if (x === undefined || x === null) {}

// good
if (x == null) {}

Prefer hasOwnProperty for checking if a key exists within an object.

Why: keys that have a falsey value but are still present might result in false positives.

var thing = { count: 0 };

if (!thing.count) // will evaluate to true, but we don’t want it to

if (!thing.hasOwnProperty('count')) // will evaluate to false, which is what we want

Wrap a conditional in braces if it’s > 1 line long.

Why: conditionals without braces over multiple lines can easily be misconstrued.

// bad
if (something) { x = true }

// good
if (something) x = true;

// bad
if (something)
  doSomethingElse();
  doAnotherThing(); // <- not part of the conditional!

// good
if (something) {
  doSomethingElse();
  doAnotherThing(); 
}

Leave a space between if and the opening (.

Why: makes it easier to pick out ifs in a large code file.

// bad
if(foo)

// good
if (foo)

ES6 Specific Conventions

=> functions

Prefer “fat arrow” functions when the function is small enough to fit on one line, or when the function’s job is to return the result of an expression.

Why: they are more concise and readable, and implicitly return when the body is an expression.

// bad
[1, 2, 3].map(function(x) { return x * 2 });

// good
[1, 2, 3].map((x) => x * 2);

Use function expressions when the function body is a block.

Why: an arrow function that spans multiple lines requires a block and an explicit return statement. Therefore, using an arrow function offers a minimally shorter expression at the expense of losing the function name in a stack trace.

// bad
[1, 2, 3].map((x) => {
  // do lots of things
  // do more things
  return x * 2;
});

// good
[1, 2, 3].map(function(x) {
  // do lots of things
  // do more things
  return x * 2;
});

Always wrap arrow function arguments in brackets.

Why: they aren’t needed when the function only takes one parameter, but it’s clearer to always include them.

// bad
[1, 2, 3].map(x => x * 2);

// good
[1, 2, 3].map((x) => x * 2);

In tests prefer arrow functions to function expressions.

Unfortunately our e2e tests run in Node.js, so don't support arrow functions (yet). When they do, we'll prefer to use them there too.

Why: keeps tests cleaner and easier to pick out the actual assertions.

// bad
it('does a thing', function() {
});

// good
it('does a thing', () => {
});

Strings

Prefer ES6 template strings to concatenation.

Why: easier to read.

// bad
var fullName = firstName + ' ' + lastName;

// good
var fullName = `${firstName} ${lastName}`;

Function Arguments

Use the spread operator over converting the arguments object.

// bad
function foo() {
  var args = [].slice.call(arguments);
}

// good
function foo(...args) {
}

ES6 Modules

Use relative paths when the target is a descendant of the path declaration, else prefer absolute paths.

Why: easier to see a file’s position, and prevents many parent directory operators.

// bad
import {foo} from '../../service/foo';

// good
import {foo} from 'client/app/service/foo';

// good
import {foo} from './foo';

Constants

let and const

Use them! Prefer const by default.

const apiUrl = 'http://example.com';