JavaScript Clean Coding Best Practices
JavaScript Clean Coding Best Practices
blog.risingstack.com/javascript-clean-coding-best-practices-node-js-at-scale
RisingStack Engineering
Writing clean code is what you must know and do in order to call yourself a
professional developer. There is no reasonable excuse for doing anything less than
your best.
“Even bad code can function. But if the code isn’t clean, it can bring a development
organization to its knees.” — Robert C. Martin (Uncle Bob)
In this blog post, we will cover general clean coding principles for naming and using
variables & functions, as well as some JavaScript specific clean coding best
practices.
Node.js at Scale is a collection of articles focusing on the needs of companies with bigger
Node.js installations and advanced Node developers. Chapters:
You know you are working on a clean code when each routine you read turns out to be
pretty much what you expected.
1/9
Now that we know what every developer should aim for, let’s go through the best
practices!
Use intention-revealing names and don’t worry if you have long variable names
instead of saving a few keyboard strokes.
If you follow this practice, your names become searchable, which helps a lot
when you do refactors or you are just looking for something.
// DON'T
let d
let elapsed
const ages = arr.map((i) => i.age)
// DO
let daysSinceModification
const agesOfUsers = users.map((user) => user.age)
Also, make meaningful distinctions and don’t add extra, unnecessary nouns to
the variable names, like its type (hungarian notation).
2/9
// DON'T
let nameString
let theUsers
// DO
let name
let users
Make your variable names easy to pronounce, because for the human mind it
takes less effort to process.
When you are doing code reviews with your fellow developers, these names are easier to
reference.
// DON'T
let fName, lName
let cntr
// DO
let firstName, lastName
let counter
Functions should do one thing. They should do it well. They should do it only. — Robert C.
Martin (Uncle Bob)
3/9
// DON'T
function getUserRouteHandler (req, res) {
const { userId } = req.params
// inline SQL query
knex('user')
.where({ id: userId })
.first()
.then((user) => res.json(user))
}
// DO
// User model (eg. models/user.js)
const tableName = 'user'
const User = {
getOne (userId) {
return knex(tableName)
.where({ id: userId })
.first()
}
}
After you wrote your functions properly, you can test how well you did with CPU
profiling – which helps you to find bottlenecks.
A function name should be a verb or a verb phrase, and it needs to communicate its
intent, as well as the order and intent of the arguments.
A long descriptive name is way better than a short, enigmatic name or a long descriptive
comment.
// DON'T
/**
* Invite a new user with its email address
* @param {String} user email address
*/
function inv (user) { /* implementation */ }
// DO
function inviteUser (emailAddress) { /* implementation */ }
4/9
// DON'T
function getRegisteredUsers (fields, include, fromDate, toDate) { /*
implementation */ }
getRegisteredUsers(['firstName', 'lastName', 'email'], ['invitedUsers'], '2016-09-
26', '2016-12-13')
// DO
function getRegisteredUsers ({ fields, include, fromDate, toDate }) { /*
implementation */ }
getRegisteredUsers({
fields: ['firstName', 'lastName', 'email'],
include: ['invitedUsers'],
fromDate: '2016-09-26',
toDate: '2016-12-13'
})
Use pure functions without side effects, whenever you can. They are really easy to use
and test.
// DON'T
function addItemToCart (cart, item, quantity = 1) {
const alreadyInCart = cart.get(item.id) || 0
cart.set(item.id, alreadyInCart + quantity)
return cart
}
// DO
// not modifying the original cart
function addItemToCart (cart, item, quantity = 1) {
const cartCopy = new Map(cart)
const alreadyInCart = cartCopy.get(item.id) || 0
cartCopy.set(item.id, alreadyInCart + quantity)
return cartCopy
}
5/9
// DON'T
// "I need the full name for something..."
function getFullName (user) {
return `${user.firstName} ${user.lastName}`
}
// DO
function renderEmailTemplate (user) {
// "I need the full name of the user"
const fullName = getFullName(user)
return `Dear ${fullName}, ...`
}
Query or modification
Functions should either do something (modify) or answer something (query), but not
both.
The stricter the rules, the less effort will go into pointing out bad formatting in code
reviews. It should cover things like consistent naming, indentation size, whitespace
placement and even semicolons.
The standard JS style is quite nice to start with, but in my opinion, it isn’t strict enough. I
can agree most of the rules in the Airbnb style.
Promises are natively available from Node 4. Instead of writing nested callbacks, you can
have chainable Promise calls.
6/9
// AVOID
asyncFunc1((err, result1) => {
asyncFunc2(result1, (err, result2) => {
asyncFunc3(result2, (err, result3) => {
console.lor(result3)
})
})
})
// PREFER
asyncFuncPromise1()
.then(asyncFuncPromise2)
.then(asyncFuncPromise3)
.then((result) => console.log(result))
.catch((err) => console.error(err))
Most of the libraries out there have both callback and promise interfaces, prefer the latter.
You can even convert callback APIs to promise based one by wrapping them using
packages like es6-promisify.
// AVOID
const fs = require('fs')
try {
callback(null, JSON.parse(data))
} catch (ex) {
callback(ex)
}
})
}
// PREFER
const fs = require('fs')
const promisify = require('es6-promisify')
readJSON('./package.json')
.then((pkg) => console.log(pkg))
.catch((err) => console.error(err))
7/9
const request = require('request-promise-native')
// PREFER
async function getExtractFromWikipedia (title) {
let body
try {
body = await request({ /* same parameters as above */ })
} catch (err) {
console.error('getExtractFromWikipedia() error:', err)
throw err
}
// or
const co = require('co')
8/9
How should I write performant code?
In the first place, you should write clean code, then use profiling to find performance
bottlenecks.
Never try to write performant and smart code first, instead, optimize the code when you
need to and refer to true impact instead of micro-benchmarks.
Although, there are some straightforward scenarios like eagerly initializing what you can
(eg. joi schemas in route handlers, which would be used in every request and adds serious
overhead if recreated every time) and using asynchronous instead of blocking code.
If you have any questions regarding clean coding, don’t hesitate and let me
know in the comments!
9/9