JavaScript code quality guide - JavaScript Application Design: A Build First Approach (2015)

JavaScript Application Design: A Build First Approach (2015)

Appendix D. JavaScript code quality guide

This style guide aims to provide the ground rules for an application’s JavaScript code, so it’s highly readable and consistent across different developers on a team. The focus is put on quality and coherence across different pieces of your application.

D.1. Module organization

This style guide assumes you’re using a module system such as CommonJS,[1] AMD,[2] ES6 Modules,[3] or any other kind of module system. For a comprehensive introduction to module systems head over to chapter 5; I’ll wait.

1 The CommonJS module specification hosts a wiki page at http://bevacqua.io/bf/commonjs.

2 RequireJS has a comprehensive article on the purpose of AMD at http://bevacqua.io/bf/amd.

3 Getting started with ES6 is much easier these days! See http://bevacqua.io/bf/es6-intro.

Module systems provide individual scoping, avoid leaks to the global project, and improve code base organization by automating dependency graph generation, instead of having to resort to manually creating tens of <script> tags.

Module systems also provide Dependency Injection patterns, which are crucial when it comes to testing individual components in isolation.

D.1.1. Strict mode

Always put "use strict";[4] at the top of your modules. Strict mode allows you to catch nonsensical behavior, discourages poor practices, and is faster because it allows compilers to make certain assumptions about your code.

4 The Mozilla Developer Network (MDN) has a great article explaining Strict Mode in JavaScript at http://bevacqua.io/bf/strict.

D.1.2. Spacing

Spacing must be consistent across every file in the application. To this end, using Editor-Config is highly encouraged. EditorConfig works by dropping an .editorconfig[5] file in your project root, and then you should install the EditorConfig plugin for your favorite text editor. Here are the defaults I suggest to get started with JavaScript indentation:

5 Learn more about EditorConfig at http://bevacqua.io/bf/editorconfig.

# editorconfig.orgroot = true

[*]

indent_style = space

indent_size = 2

end_of_line = lf

charset = utf-8

trim_trailing_whitespace = true

insert_final_newline = true

[*.md]

trim_trailing_whitespace = false

EditorConfig can take care of indentation transparently, and everyone can consistently produce the right amount of tabs or spaces by pressing the tab key. Settling for either tabs or spaces is up to the particularities of a project, but I recommend using two spaces for indentation.

Spacing doesn’t only entail tabbing, but also the spaces before, after, and in between arguments of a function declaration. This kind of spacing is typically highly difficult to get right, and it’ll be hard for most teams to even arrive at a scheme that will satisfy everyone.

function () {}

function( a, b ){}

function(a, b) {}

function (a,b) {}

Try to keep these differences to a minimum, but don’t put much thought into it either.

Where possible, improve readability by keeping lines below the 80-character mark.

D.1.3. Semicolons

Automatic Semicolon Insertion (ASI) isn’t a feature. Don’t rely on it.[6] It’s super complicated[7] and there’s no practical reason to burden all of the developers in a team for not possessing the frivolous knowledge of how ASI works. Avoid headaches; avoid ASI. Always add semicolons where needed.

6 Ben Alman has good advice about why you should use semicolons instead of omitting them at http://bevacqua.io/bf/semicolons.

7 A guide to the inner workings of automatic semicolon insertion (ASI) is available at http://bevacqua.io/bf/asi.

D.1.4. Linting

Given that JavaScript doesn’t require a compilation step that would take care of undeclared variables, linting is almost a necessity. Again, don’t use a linter that’s super-opinionated about how the code should be styled, like jslint[8] is. Instead use something more lenient, like jshint[9] oreslint[10]. Here are a few tips when using JSHint:

8 JSLint, the original JavaScript linter, can still be used online today at http://bevacqua.io/bf/jslint.

9 JSHint is a modern alternative that’s popularly observed in build processes. Find it at http://bevacqua.io/bf/jshint.

10 ESLint is yet another lint tooling effort, aiming to be less concerned about style checking. Find it at http://bevacqua.io/bf/eslint.

· Declare a .jshintignore file and include node_modules, bower_components, and so on.

· You can use a .jshintrc file like the one below to keep your rules together:

{

"curly": true,

"eqeqeq": true,

"newcap": true,

"noarg": true,

"noempty": true,

"nonew": true,

"sub": true,

"undef": true,

"unused": true,

"trailing": true,

"boss": true,

"eqnull": true,

"strict": true,

"immed": true,

"expr": true,

"latedef": "nofunc",

"quotmark": "single",

"indent": 2,

"node": true

}

By no means are these rules the ones you should stick to, but it’s important to find the sweet spot between not linting at all, and not being super-obnoxious about coding style. If left unchecked, you may succumb to common mistakes such as missing semicolons or improperly closing string quotes, but if you go overboard you may find that your team spends more time taking care of code style than writing meaningful code.

D.2. Strings

Strings should always be quoted using the same quotation mark. Use ' or " consistently throughout your code base. Ensure the team is using the same quotation mark in every portion of JavaScript that’s authored.

Bad Strings

var message = 'oh hai ' + name + "!";

Good Strings

var message = 'oh hai ' + name + '!';

Usually you’ll be a happier JavaScript developer if you hack together a parameter-replacing method such as util.format in Node.[11] That way it’ll be far easier to format your strings, and the code looks cleaner too.

11 The documentation for Node’s util.format can be found at http://bevacqua.io/bf/util.format.

Better Strings

var message = util.format('oh hai %s!', name);

You can implement something similar using the following piece of code:

function format () {

var args = [].slice.call(arguments);

var initial = args.shift();

function replacer (text, replacement) {

return text.replace('%s', replacement);

}

return args.reduce(replacer, initial);

}

To declare multiline strings, particularly when talking about HTML snippets, it’s sometimes best to use an array as a buffer and then join its parts. The string concatenating style may be faster but it’s also much harder to keep track of:

var html = [

'<div>',

format('<span class="monster">%s</span>', name),

'</div>'

].join('');

With the array builder style, you can also push parts of the snippet and then join everything together at the end. This is what string templating engines such as Jade[12] prefer to do.

12 Learn more about templating in Jade by visiting their GitHub repository at http://bevacqua.io/bf/jade.

D.2.1. Variable declaration

Always declare variables in a consistent manner, and at the top of their scope. Keeping variable declarations to one-per-line is encouraged. Comma-first, a single var statement, multiple var statements, it’s all fine, but be consistent across the project. Ensure that everyone on your team follows the style guide, for consistency.

Inconsistent declaration

var foo = 1,

bar = 2;

var baz;

var pony;

var a

, b;

or

var foo = 1;

if (foo > 1) {

var bar = 2;

}

Note that the following example is okay not only because of its style, but also because the statements are consistent with each other.

Consistent Declarations

var foo = 1;

var bar = 2;

var baz;

var pony;

var a;

var b;

var foo = 1;

var bar;

if (foo > 1) {

bar = 2;

}

Variable declarations not immediately assigned a value can share the same line of code.

Acceptable declaration

var a = 'a';

var b = 2;

var i, j;

D.3. Conditionals

Brackets are enforced. This, together with a reasonable spacing strategy, will help you avoid mistakes such as Apple’s SSL/TLS bug.[13]

13 A detailed report on Apple’s “GOTO Fail” bug can be found at http://bevacqua.io/bf/gotofail.

Bad conditionals

if (err) throw err;

Good conditionals

if (err) {

throw err;

}

Avoid using == and != operators; always favor === and !==. These operators are called the “strict equality operators,” whereas their counterparts will attempt to cast the operands[14] into the same value type. If possible, try to keep even single-statement conditionals in a multiline format.

14 Equality operators have a dedicated page on MDN at http://bevacqua.io/bf/equality.

Bad coercing equality

function isEmptyString (text) {

return text == '';

}

isEmptyString(0);

// <- true

Good strict equality

function isEmptyString (text) {

return text === '';

}

isEmptyString(0);

// <- false

D.3.1. Ternary operators

Ternary operators are fine for clear-cut conditionals, but unacceptable for confusing choices. As a rule, if you can’t eye-parse it as fast as your brain can interpret the text that declares the ternary operator, chances are it’s probably too complicated for its own good.

jQuery is a prime example of a code base that’s filled with nasty ternary operators.[15]

15 A few examples of ternary operator misuse in jQuery can be found at http://bevacqua.io/bf/jquery-ternary.

Bad ternary operators

function calculate (a, b) {

return a && b ? 11 : a ? 10 : b ? 1 : 0;

}

Good ternary operators

function getName (mobile) {

return mobile ? mobile.name : 'Generic Player';

}

In cases that may prove confusing, use if and else statements instead.

D.3.2. Functions

When declaring a function always use the function declaration form[16] instead of function expressions.[17] If you try to use your function expressions before they’re assigned to a variable, you’ll get an error. In contrast, function declarations are hoisted[18] to the top of the scope, meaning they’ll work regardless of where you place them in your code. You can learn all the details about hoisting in chapter 5.

16 StackOverflow has an answer that covers function declarations at http://bevacqua.io/bf/fn-declaration.

17 You can find a concise definition of function expressions on MDN at http://bevacqua.io/bf/fn-expr.

18 Variable hoisting is explained in the code samples found at http://bevacqua.io/bf/hoisting.

Using expressions is bad

var sum = function (x, y) {

return x + y;

};

Using declarations is good

function sum (x, y) {

return x + y;

}

That being said, there’s nothing wrong with function expressions that curry another function.[19]

19 John Resig explains how to partially apply functions on his blog at http://bevacqua.io/bf/partial-application.

Currying is good

var plusThree = sum.bind(null, 3);

Keep in mind that function declarations will be hoisted[20] to the top of the scope, so it doesn’t matter what order they’re declared in. That being said, you should always keep them at the top level in a scope, and always avoid placing them inside conditional statements.

20 Variable hoisting is explained in the code samples found at http://bevacqua.io/bf/hoisting.

Bad functions

if (Math.random() > 0.5) {

sum(1, 3);

function sum (x, y) {

return x + y;

}

}

Good functions

if (Math.random() > 0.5) {

sum(1, 3);

}

function sum (x, y) {

return x + y;

}

Or

function sum (x, y) {

return x + y;

}

if (Math.random() > 0.5) {

sum(1, 3);

}

If you need a “no-op” method, you can use either Function.prototype, or function noop () {}. Ideally a single reference to noop is used throughout the application. Whenever you have to manipulate the arguments object, or other array-likes, cast them to an array.

Bad array-like loop

var divs = document.querySelectorAll('div');

for (i = 0; i < divs.length; i++) {

console.log(divs[i].innerHTML);

}

Good array-like loop

var divs = document.querySelectorAll('div');

[].slice.call(divs).forEach(function (div) {

console.log(div.innerHTML);

});

However, be aware that there’s a substantial performance hit[21] in V8 environments when using this approach on arguments. If performance is a major concern, avoid casting arguments with slice and use a for loop instead.

21 See a great article on optimizing manipulation of function arguments at http://bevacqua.io/bf/arguments.

Bad arguments accessor

var args = [].slice.call(arguments);

Better arguments accessor

var i;

var args = new Array(arguments.length);

for (i = 0; i < args.length; i++) {

args[i] = arguments[i];

}

Never declare functions inside of loops.

Bad inline functions

var values = [1, 2, 3];

var i;

for (i = 0; i < values.length; i++) {

setTimeout(function () {

console.log(values[i]);

}, 1000 * i);

}

or

var values = [1, 2, 3];

var i;

for (i = 0; i < values.length; i++) {

setTimeout(function (i) {

return function () {

console.log(values[i]);

};

}(i), 1000 * i);

}

Better extract the function

var values = [1, 2, 3];

var i;

for (i = 0; i < values.length; i++) {

wait(i);

}

function wait (i) {

setTimeout(function () {

console.log(values[i]);

}, 1000 * i);

}

Or even better, use .forEach, which doesn’t have the same caveats as declaring functions in for loops.

Even better, functional arrays with foreach

[1, 2, 3].forEach(function (value, i) {

setTimeout(function () {

console.log(value);

}, 1000 * i);

});

Named function vs anonymous

Whenever a method is nontrivial, make the effort to use a named function expression rather than an anonymous function. This makes it easier to pinpoint the root cause of an exception when analyzing stack traces.

Bad, anonymous functions

function once (fn) {

var ran = false;

return function () {

if (ran) { return };

ran = true;

fn.apply(this, arguments);

};

}

Good, named function

function once (fn) {

var ran = false;

return function run () {

if (ran) { return };

ran = true;

fn.apply(this, arguments);

};

}

Avoid keeping indentation levels from raising more than necessary by using guard clauses instead of flowing if statements.

Bad

if (car) {

if (black) {

if (turbine) {

return 'batman!';

}

}

}

or

if (condition) {

// 10+ lines of code

}

Good

if (!car) {

return;

}

if (!black) {

return;

}

if (!turbine) {

return;

}

return 'batman!';

or

if (!condition) {

return;

}

// 10+ lines of code

D.3.3. Prototypes

Hacking the prototype of native types should be avoided at all costs; use methods instead. If you must extend the functionality in a native type, try using poser[22] instead. Poser provides out-of-context native type references that you can safely build upon and extend.

22 Poser provides out-of-context native type references that you can safely build upon and extend. For more information, see http://bevacqua.io/bf/poser.

Bad

String.prototype.half = function () {

return this.substr(0, this.length / 2);

};

Good

function half (text) {

return text.substr(0, text.length / 2);

}

Avoid prototypical inheritance models unless you have a good performance reason to justify yourself:

· They are way more verbose than using plain objects.

· They cause headaches when creating new objects.

· They need a closure to hide valuable private state of instances.

· Just use plain objects instead.

D.3.4. Object literals

Instantiate using the Egyptian notation {}. Use factories instead of constructors. Here’s a proposed pattern for you to implement objects in general:

function util (options) {

// private methods and state go here

var foo;

function add () {

return foo++;

}

function reset () { // note that this method isn't publicly exposed

foo = options.start || 0;

}

reset();

return {

// public interface methods go here

uuid: add

};

}

D.3.5. Array literals

Instantiate using the square bracketed notation []. If you have to declare a fixed-dimension array for performance reasons, then it’s fine to use the new Array(length) notation instead.

Arrays in JavaScript have a rich API that you should take advantage of. You can start with array manipulation basics[23] and then move on to more advanced use cases. For example, you could use the .forEach method to iterate over all of the items in a collection.

23 An introductory article on JavaScript arrays is available on my blog at http://bevacqua.io/bf/arrays.

The following list shows basic operations you can perform on arrays:

· Use .push to insert items at the end of a collection or .shift to insert them at the beginning.

· Use .pop to get the last item and remove it from the collection at the same time or use .unshift to do the same for the first item.

· Master .splice to remove items by index, or to insert items at a specific index, or to do both at the same time!

Also learn about and use the functional collection manipulation methods! These can save you a ton of time that you’d otherwise spend doing the operations by hand. Here are a few examples of things you can do:

· Use .filter to discard uninteresting values.

· Use .map to transpolate array values into something else.

· Use .reduce to iterate over an array and produce a single result.

· Use .some and .every to assert whether all array items meet a condition.

· Use .sort to arrange the elements in a collection.

· Use .reverse to invert the order in the array.

The Mozilla Developer Network (MDN) has thoroughly documented all of these methods and more at https://developer.mozilla.org/.

D.4. Regular expressions

Keep regular expressions in variables; don’t use them inline. This will vastly improve readability.

Bad regular expressions

if (/\d+/.test(text)) {

console.log('so many numbers!');

}

Good regular expressions

var numeric = /\d+/;

if (numeric.test(text)) {

console.log('so many numbers!');

}

Also, learn to write regular expressions[24] and what they do. Then you can also visualize them online.[25]

24 There’s an introductory article on regular expressions on my blog at http://bevacqua.io/bf/regex.

25 Regexper lets you visualize how any regular expression works at http://bevacqua.io/bf/regexper.

D.4.1. Debugging statements

Preferably put your console statements into a service that can easily be disabled in production. Alternatively, don’t ship any console.log printing statements to production distributions.

D.4.2. Comments

Comments aren’t meant to explain what the code does. Good code is supposed to be self-explanatory. If you’re thinking of writing a comment to explain what a piece of code does, chances are you need to change the code itself. The exception to that rule is explaining what a regular expression does. Good comments are supposed to explain why code does something that may not seem to have a clear-cut purpose.

Bad comments

// create the centered container

var p = $('<p/>');

p.center(div);

p.text('foo');

Good comments

var container = $('<p/>');

var contents = 'foo';

container.center(parent);

container.text(contents);

megaphone.on('data', function (value) {

container.text(value); // the megaphone periodically emits updates for container

});

or

var numeric = /\d+/; // one or more digits somewhere in the string

if (numeric.test(text)) {

console.log('so many numbers!');

}

Commenting out entire blocks of code should be avoided entirely; that’s why you have version control systems in place!

D.4.3. Variable naming

Variables must have meaningful names so that you don’t have to resort to commenting what a piece of functionality does. Instead, try to be expressive while succinct, and use meaningful variable names:

Bad naming

function a (x, y, z) {

return z * y / x;

}

a(4, 2, 6);

// <- 3

Good naming

function ruleOfThree (had, got, have) {

return have * got / had;

}

ruleOfThree(4, 2, 6);

// <- 3

D.4.4. Polyfills

A polyfill is a piece of code that transparently enables your application to use modern features in older browsers. Where possible use the native browser implementation and include a polyfill that provides that same behavior[26] to unsupported browsers. This makes the code easier to work with and less involved in hackery to make things just work.

26 Remy Sharp concisely explains what a polyfill is at http://bevacqua.io/bf/polyfill.

If you can’t patch a piece of functionality with a polyfill, then wrap all uses of the patching code[27] in a globally available implementation that’s accessible from anywhere in the application.

27 I’ve written an article on developing high quality modules that touches on the implementation-wrapping subject at http://bevacqua.io/bf/hq-modules.

D.4.5. Everyday tricks

Creating default values

Use || to define a default value. If the left-hand value is falsy[28] then the right-hand value will be used.

28 In JavaScript, falsy values are treated as false in conditional statements. Falsy values are ‘’, null, undefined, and 0. For more information, see http://bevacqua.io/bf/casting.

function a (value) {

var defaultValue = 33;

var used = value || defaultValue;

}

Using bind to partially apply functions

Use .bind to partially apply[29] functions:

29 John Resig, of jQuery fame, has an interesting article on partial JavaScript functions at http://bevacqua.io/bf/partial-application.

function sum (a, b) {

return a + b;

}

var addSeven = sum.bind(null, 7);

addSeven(6);

// <- 13

Array.prototype.slice.call to cast array-like objects to arrays

Use Array.prototype.slice.call to cast array-like objects to true arrays:

var args = Array.prototype.slice.call(arguments);

Event emitters on all things

Use event emitters[30] on all the things! This pattern helps you to decouple implementations from messaging between different objects or application layers:

30 Contra provides an easy-to-use event emitter implementation at http://bevacqua.io/bf/contra.emitter.

var emitter = contra.emitter();

body.addEventListener('click', function () {

emitter.emit('click', e.target);

});

emitter.on('click', function (elem) {

console.log(elem);

});

// simulate click

emitter.emit('click', document.body);

Function.prototype as a no-op

Use Function.prototype as a “no-op:”

function (cb) {

setTimeout(cb || Function.prototype, 2000);