Master Clean Code: JavaScript Best Practices for Readable, Maintainable Code
This article presents practical JavaScript clean‑code guidelines—including intention‑revealing naming, single‑purpose functions, pure functions, proper async handling, and linting standards—to help developers write more readable, maintainable, and error‑free code.
With only six days left until the Lunar New Year, programmers reflect on the past year’s code and aim to improve it by embracing clean‑code principles.
What Is Clean Code
Clean code is written for future selves and collaborators, not just for machines.
JavaScript Clean Code Best Practices
How to name variables?
Use intention‑revealing names
Don’t worry about long names; clear intent makes searching and refactoring easier.
// DON'T
let d
let elapsed
const ages = arr.map((i) => i.age)
// DO
let daysSinceModification
const agesOfUsers = users.map((user) => user.age)Avoid meaningless extra information in variable names
// DON'T
let nameString
let theUsers
// DO
let name
let usersMake variable names readable
Readable names are easier for the brain and for code reviews.
// DON'T
let fName, lName
let cntr
let full = false
if (cart.size > 100) {
full = true
}
// DO
let firstName, lastName
let counter
const MAX_CART_SIZE = 100
// ...
const isFull = cart.size > MAX_CART_SIZEHow to write functions?
Functions should do one thing and do it well
// 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()
}
}
// route handler (eg. server/routes/user/get.js)
function getUserRouteHandler(req, res) {
const { userId } = req.params
User.getOne(userId)
.then((user) => res.json(user))
}Use long, descriptive names
Function names should be verbs or verb phrases that clearly convey intent; longer descriptive names are preferable to cryptic short ones.
// DON'T
/**
* Invite a new user with its email address
* @param {String} user email address
*/
function inv(user) { /* implementation */ }
// DO
function inviteUser(emailAddress) { /* implementation */ }Avoid long parameter lists
Prefer passing a single object and using destructuring, which also simplifies handling optional parameters.
// 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'
})Avoid side effects
Prefer pure functions without side effects; they are easier 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
}
// or by invert the method location
// you can expect that the original object will be mutated
// addItemToCart(cart, item, quantity) -> cart.addItem(item, quantity)
const cart = new Map()
Object.assign(cart, {
addItem (item, quantity = 1) {
const alreadyInCart = this.get(item.id) || 0
this.set(item.id, alreadyInCart + quantity)
return this
}
})Organize functions by call hierarchy
Higher‑level functions should appear earlier in the file; lower‑level helpers later, making the flow natural.
// DON'T
// "I need the full name for something..."
function getFullName(user) {
return `${user.firstName} ${user.lastName}`
}
function renderEmailTemplate(user) {
// "oh, here"
const fullName = getFullName(user)
return `Dear ${fullName}, ...`
}
// DO
function renderEmailTemplate (user) {
// "I need the full name of the user"
const fullName = getFullName(user)
return `Dear ${fullName}, ...`
}
// "I use this for the email template rendering"
function getFullName (user) {
return `${user.firstName} ${user.lastName}`
}Separate queries from modifications
Functions should either modify state or return a result, but not both.
What to do when everyone has their own coding style?
Use project‑ or company‑level linting rules to enforce consistent naming, indentation, spacing, and semicolons; stricter rules reduce the time spent on formatting issues during code review.
Standard JS can be a reference, but Airbnb’s style guide is more comprehensive.
How to write asynchronous code?
Always use Promises instead of callbacks.
// 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 libraries provide both interfaces; prefer the Promise version. You can wrap callback‑based APIs with es6‑promisify.
// AVOID
const fs = require('fs')
function readJSON(filePath, callback) {
fs.readFile(filePath, (err, data) => {
if (err) {
return callback(err)
}
try {
callback(null, JSON.parse(data))
} catch (ex) {
callback(ex)
}
})
}
readJSON('./package.json', (err, pkg) => { console.log(err, pkg) })
// PREFER
const fs = require('fs')
const promisify = require('es6-promisify')
const readFile = promisify(fs.readFile)
function readJSON(filePath) {
return readFile(filePath)
.then((data) => JSON.parse(data))
}
readJSON('./package.json')
.then((pkg) => console.log(pkg))
.catch((err) => console.error(err))Then use async/await or generators + co to achieve synchronous‑like flow.
// PREFER
async function getExtractFromWikipedia(title) {
let body
try {
body = await request({ /* same parameters as above */ })
} catch (err) {
console.error('getExtractFromWikipedia() error:', err)
throw err
}
const extracts = Object.keys(body.query.pages).map((key) => body.query.pages[key].extract)
return extracts[0]
}
// or
const co = require('co')
const getExtractFromWikipedia = co.wrap(function* (title) {
let body
try {
body = yield request({ /* same parameters as above */ })
} catch (err) {
console.error('getExtractFromWikipedia() error:', err)
throw err
}
const extracts = Object.keys(body.query.pages).map((key) => body.query.pages[key].extract)
return extracts[0]
})
getExtractFromWikipedia('Robert Cecil Martin')
.then((robert) => console.log(robert))Should I write high‑performance code?
First aim for clean code, then use profiling tools to locate bottlenecks; avoid premature optimization.
Optimize obvious cases, such as lazy‑loading frequently used services or preferring async over blocking synchronous calls.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Node Underground
No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
