We are in the business of maintaining code, therefore we should strive to write code as consistently and maintainable as possible.
- Tabs vs Spaces
- Line Length
- Whitespace
- Braces
- Parenthesis
- Variable and Property Names
- Constructor Names
- Named Functions
- Function Spacing
- Ternary Spacing
- Semi-colons
- Variables
- Comma First
- Equality
- Self
- Errors
- Return Early
new
is Optional- Inline Documentation
- Modifying Native Prototypes
- CoffeeScript
Node.js core uses two space indents, so should we.
80 characters max.
No trailing whitespace allowed. Clean up after yourself.
Place opening braces on the same line as the statement.
if (areWeWritingJava)
{
// hmmmm
}
if (nicelyDone) {
// ok!
}
Leave space before and after the parenthesis for if/while/for/catch/switch/do
// inconsistent
if(weAreHavingFun){
console.log('Wheeee!');
}
// consistent
if (weAreHavingFun) {
console.log('Wheeee!');
}
Use lower camel case and be descriptive. Aim for one word descriptions if possible.
// inconsistent
var plan_query = model.findById(plan_id);
// consistent
var planQuery = model.findById(planId);
Constructors should use upper camel case to hint to its intended use.
function Movie(id, title) {
if (!(this instanceof Movie)) {
return new Movie(id, title);
}
this.id = id;
this.title = title;
};
var gravity = new Movie(1, 'Gravity');
Name your functions. It aids in comprehension when coming back to it six months later and provides more descriptive stack traces.
model.findOne(params, function(err, doc) {
// not very helpful
});
model.findOne(params, function userCallback(err, doc) {
// more helpful
});
When executing a function, leave no space between it's name and parens.
eatIceCream (); // inconsistent
eatIceCream(); // consistent
Leave a space between your function
declaration/expression parenthesis and the opening {
.
function eatIceCream(){
// inconsistent
};
function eatIceCream() {
// consistent
};
There should be no empty lines between the function name and the first line of code.
// inconsistant
function eatIceCream() {
var dessert = bowl().of(iceCream);
human.eat(desert);
}
// consistant
function eatIceCream() {
var dessert = bowl().of(iceCream);
human.eat(desert);
}
Break ternarys across multiple indented lines. It's easier to switch the values around when you come back to it later and realize a mistake.
// less maintainable
var docs = Array.isArray(response) ? response : [response];
// more maintainable
var docs = Array.isArray(res)
? res
: [res];
Also, no nested ternary expressions permitted. Business logic is often
confusing enough on its own. If necessary, break out the logic into additional
if/else
blocks.
// do not nest
var msg = first
? isValid(thing)
? 'ok'
: 'fail'
: 'confusing';
// better
var msg = 'not so confusing';
if (first) {
msg = isValid(thing)
? 'ok'
: 'fail';
}
Use em.
var semis = function() {
// codez
}
['property'].forEach(console.log) // TypeError: Cannot call method 'forEach' of undefined
var semis = function() {
// codez
}; // <--------
['property'].forEach(console.log) // property 0 [ 'property' ]
It's really great if you take the extra time to learn where they're actually necessary, you may just be surprised how few places there are. I personally think it's easier to learn the rules and use them only where they're required, but for even mid-size teams, it's simpler to just use 'em and move on.
When declaring multiple variables, use "var first" style.
var fix = true;
var nice = 'you best';
var woot = require('woot');
This makes for easier copy/paste later on and less futzing around with semi-colons and commas.
Use comma first.
// arrays
var array = [
'my'
, 'array'
, 'is'
, 'neat'
];
// nested arrays
var nestedArray = [
['this', 'is', 'a', 'nested', 'array']
, ['this', 'is', 'too']
];
// objects with on property may be inlined
var simpleObject = { x: 1 };
// objects with more than one property should use comma first
var avgObject = {
first: 1
, second: 2
};
// nested objects
var nestedObjects = {
my: 'object'
, is: {
nested: 'indeed'
, you: 'see'
}
};
When comparing a variable against a literal or native type, always place the variable after the equality check.
if (false === myVariable) ..
if ('string' == typeof myVariable) ..
This way we discourage progamming errors due to unintentional assignment.
if (myVariable = false) // oops
if (typeof myVariable = 'string') // oops
Name variable references to this
"self".
Dancer.prototyp.selfie = function selfie() {
var self = this;
return function() {
console.log(self);
};
};
If you must throw an error, make sure what you throw is an instance of Error
.
For example:
if (!exists) {
throw 'invalid!'; // not very helpful
}
if (!exists) {
throw new Error('invalid!'); // more helpful
}
Each instance of Error
has a stack
property which aids in debugging.
try {
throw new Error('hmmmm');
} catch (err) {
console.error(err.stack);
}
Can I throw:
strings
? nopenumbers
? noarrays
? naddaobjects
that have a message property? forget itup
? please not on me
Same goes for callbacks. If an error condition is identified, pass an instance of Error
to the callback.
app.use(function(req, res, next) {
var err = failedValidation(req)
? new Error('could not parse request')
: undefined;
next(err);
});
Return early whereever you can to reduce nesting and complexity.
function checkSomething(req, res, next) {
if (req.params.id) {
if (isValidId(req.params.id)) {
find(req.params.id, function(err, doc) {
if (err) return next(err);
res.render('plan', doc);
});
} else {
next(new Error('bad id'));
}
} else {
next();
}
};
// return early
function checkSomething(req, res, next) {
if (!req.params.id) {
return next();
}
if (!isValidId(req.params.id)) {
return next(new Error('bad id'));
}
find(req.params.id, function(err, doc) {
if (err) return next(err);
res.render('plan', doc);
});
};
In constructors, first check if the function was called without the new
keyword and correct it if possible by returning a new instance of the
constructor and passing along the arguments.
This guards against programmer error with negligable downside.
function Script(code) {
this.code = code;
};
var script = Script('var x = 1');
console.log(script); // undefined
// better
function Script(code) {
if (!(this instanceof Script)) {
return new Script(code);
}
this.code = code;
};
var script = Script('var x = 1');
console.log(script); // { code: 'var x = 1' }
Each function should have corresponding JSDoc-style comments that include parameters and return values. Examples are always appreciated.
/**
* Adds two numbers.
*
* Example
*
* var result = add(39, 12);
*
* @param {Number} num1
* @param {Number} num2
* @return {Number}
*/
function add(num1, num2) {
return num1 + num2;
};
Don't do it. It causes debugging headaches, breaks expected behavior and introduces potential incompatibility with new versions of V8. There's always a better way.
// please no
Array.prototype.contains = function(needle) {
return this.indexOf(needle) > -1;
};
// For example, create a helper module with a function that accepts an array:
// helpers/array.js
exports.contains = function(array, needle) {
return array.indexOf(needle) > -1;
};
Is banned. Just learn JavaScript. Really. Its a simple language.