Writing code is easy. Writing clear, concise and coherent code is not, especially in Javascript. Here are some tips that can help developers to stay sane.
Writing legible and tidy code help towards having readable code that can greatly improve how you refactor code later on. Avoid having too much dependencies within each of your modules (think about testing when doing this)
When collaborating with other members (organization/open source/etc.), always use some form of linting before committing code. Whether it's eslint, jshint etc, doesn't matter. Just use one.
- 80 characters length
- tabspace: 2 (With js's callback natures, you don't want to start scrolling horizontally)
Always have a root file (index.js/app.js/server.js). It gives clear guidance to readers as to where to begin.
tests
: always have tests for your code, and put them within the tests directory
lib
: holds all library (middlewares, and helpers)
Javascript is an extremely "annoying" (yes, in quotes) language. It is a very small language that can be picked up quickly. However to master it, there are a lot to learn.
Event Loop:
for (var i = 0; i < 10; i++) {
setImmediate(function() {
console.log(i);
});
}
Function Binding:
var test = {};
function a() {
console.log(this);
}
a.bind(test);
apply
vs call
function terrible(a, b, cb) {
// Don't do this!
if (a === 10) {
cb(null, a + b);
} else {
someAsyncFunc(a, b, cb);
}
}
function good(a, b, cb) {
if (a === 10) {
process.nextTick(function() {
cb(null, a + b);
});
} else {
someAsyncFunc(a, b, cb);
}
}
Synchronous functions should always use return
and avoid callback
to be
called upon completion.
function sync(a, b) {
return a + b;
}
Asynchronous should rely on either Promise
or callback
.
function async(a, b, callback) {
process.nextTick(function() {
callback(null, a, b);
});
}
function promisified(a, b) {
return Promise.resolve(a + b);
}
function test(cb) {
cb(err, args);
}
async
library is only meant to use with functions that are truly asynchronous.
Do not use it with synchronous ones!
(Reference: #75)
async.map(arr, function(item, callback) {
// Don't do this!
callback(null, item);
}, function() {
console.log('sad');
});
// Good
async.map(arr, function(item, callback) {
process.nextTick(function() {
callback(null, item);
});
}, function() {
console.log('happy now')
});
Avoid using nested asynchronous calls in your functions; they would only make your functions more complicated and less attractive/readable.
function test(callback) {
async.series([], function(err) {
if (err) {
return callback(err);
}
// terrible!
async.waterfall([], function() {
});
});
}
Simply just simplify by using 1 asynchronous
calls.
lodash
is a utility library that can be used for
object
and array
. Do not use the corresponding async
(map/every/etc.), as it's meant for something totally different.
var _ = require('lodash');
_.each(['a','b'], function(item) {
console.log(item);
});
return
-ing earlier allows a better coding style with clear and concise logical sequence.
function funcA() {
if (condition) {
return;
}
// a lot of code....
}
This allows clear signals that the function has been terminated, and avoid
invoking the callback function multiple times. It even reduces the
use of else
in many case.
function async(fn) {
return http.get('http://example.com', function() {
fn();
});
console.log('this will not be printed');
}
Always be explicit in function calls, so that everyone would be clear as to which variables are to be passed around.
// Avoid this!
function(fn) {
fs.readFile('test.txt', fn);
}
// This would be better
function(fn) {
fs.readFile('test.txt', function(err, data) {
fn(err, data);
});
}
var a = function() {};
// vs
function a() {
}
The former is assigning an anonymous function to a variable a
, while latter is defining a function named a. The biggest difference between the two relies on variable hoisting
Whilst this might seems unnecessary, it definitely helps when you want to see the better stack trace.
var a = function a() {
throw new Error('e');
};
try {
a();
} catch(e) {
console.log(e);
}
This doesn't mean that it's wrong to export a function. It really just means that a function isn't ideal to be exported, if it doesn't do anything computational.
node.js, by default, caches the output of the module.exports
but when it exports a function, that function is being cached, not the output.
module.exports = function() {
return {
a: 'b'
};
};
// vs.
module.exports = {
a: 'b'
};
When you deal with promises, you just need to return a promise.
var Promise = require('bluebird');
function test() {
return Promise.resolve();
}
// The return of this function will be another promise too. You do not need to wrap a() within a `new promise`
function a() {
return test().then(function() {
return Promise.resolve();
});
}
Writing test should always be done. It will greatly help you in the long run. When writing unit test, I usually use BDD (Behavioural) with mocha, and have istanbul for code coverage.
describe('as a user', function() {
it('should be able to retrieve expected data', function() {
// can pass an optional `done` for asynchronous tests
});
});
Think about testing when you code. It can help towards having a good coverage as well as concise functions.
One suggestion would be to write test in format of scenarios.
describe('when user hits endpoint', function() {
it('should see the login page', function() {
assert.ok();
});
});
I would highly suggest reading the style guide by @felixge.
Should you feel that there are other pet peeves you'd like to share, feel free to fork it, commit and create a PR.