JavaScript Best Practices for Readable and Maintainable Code

Achieving the three Rs of software development: Refactorability, Reusability, and Readability

Good code is easy to understand and maintain. It achieves the three Rs of Software Architecture: Refactorability, Reusability & Readability.

Achieving the three Rs has always been important but has become even more so, in recent years. Products are often implemented in multiple ways for a variety of audiences, devices, and platforms. All this makes sharing and reusing components between projects, an absolute must.

Code that is not easy to isolate, reuse, and understand will not be adopted by other projects.

You can check your own code by publishing components to Bit. If you’ve built it right, you’ll find it very easy to publish components and reuse them in other projects.

Variables

Meaningful Names

Make sure your variables are named meaningfully. This reduces the need for additional comments as your code speaks for itself.

//BAD
cosnt ddmmyyyy = new Date();//GOOD
const date = new Date();

Searchable Variable Names

We spend more time reading code than we do writing code. That is why it’s important that it is readable and searchable. If you see a value and have no idea what it does or is supposed to do, that would be confusing on the reader’s end.

//BAD
//Reader would have no clue on what 86400000 is
setTimeout(randomFunction, 86400000);//GOOD
// Declare them as capitalized named constants.
const MILLISECONDS_IN_A_DAY = 86_400_000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

Avoid Mental Mapping

Don’t force people to memorize the variable context. Variables should be understood even when the reader has not managed to follow the whole history of how they came to be.

// BAD
const names = ["John", "Jane", "Joseph"];
names.forEach(v => {
doStuff();
doSomethingExtra();
// ...
// ...
// ...
// What is this 'v' for?
dispatch(v);
});// GOOD
const names = ["John", "Jane", "Joseph"];
names.forEach(name => {
doStuff();
doSomethingExtra();
// ...
// ...
// ...
// 'name' makes sense now
dispatch(name);
});

Do Not Add Unwanted Context

If your class or object name tells you what it is, do not include that in the variable name.

// BAD
const Book = {
bookName: "Programming with JavaScript",
bookPublisher: "Penguin",
bookColour: "Yellow"
};
function wrapBook(book) {
book.bookColour = "Brown";
}// GOOD
const Book = {
name: "Programming with JavaScript",
publisher: "Penguin",
colour: "Yellow"
};
function wrapBook(book) {
book.colour = "Brown";
}

Use Default Arguments

Instead of using the short-circuit approach, we provide default arguments to variables to end up with a much cleaner output.

// BAD
function addEmployeeType(type){
const employeeType = type || "intern";
//............
}// GOOD
function addEmployeeType(type = "intern"){
//............
}

Use Strong Type Checks

Use === instead of == . This would help you avoid all sorts of unnecessary problems later on. If not handled properly, it can dramatically affect the program logic.

0 == false // true
0 === false // false
2 == "2" // true
2 === "2" // false

Functions

Use Descriptive Names That Speak for Themselves

Considering functions that represent a certain behavior, a function name should be a verb or a phrase fully exposing the intent behind it as well as the intent of the arguments. Their name should say what they do.

//BADfunction sMail(user){
//........
}//GOODfunction sendEmail(emailAddress){
//.........
}

Minimal Function Arguments

Ideally, you should avoid a long number of arguments. Limiting the number of function parameters would help it easier to test your function.

One or two arguments is the ideal case, and three should be avoided if possible. Anything more than that should be consolidated. Usually, if you have more than two arguments then your function is trying to do too much. In cases where it’s not, most of the time a higher-level object will suffice as an argument.

//BAD
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);//GOOD
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});

Functions Should Only Do One Thing

This is one of the most important rules in software engineering. When your function does more than one thing, it is harder to test, compose and reason about. When you isolate a function to just one action, it can be refactored easily and your code will read much much cleaner.

Functions should do one thing. They should do it well. They should do it only. — Robert C. Martin (Uncle Bob)

//BAD
function notifyListeners(listeners) {
listeners.forEach(listener => {
const listenerRecord = database.lookup(listener);
if (listenerRecord.isActive()) {
notify(listener);
}
});
}//GOOD
function notifyActiveListeners(listeners) {
listeners.filter(isListenerActive).forEach(notify);
}
function isListenerActive(listener) {
const listenerRecord = database.lookup(listener);
return listenerRecord.isActive();
}

Remove Duplicate Code

You should do your best to avoid code duplication. Writing the same code more than once is not only wasteful when writing it the first time but even more so when trying to maintain it. Instead of having one change affect all relevant modules, you have to find all duplicate modules and repeat that change.

Oftentimes duplication in code happens because two or more modules have slight differences that make it because you have two or more slightly different things that share much in common.

Small differences force you to have a very similar modules. Removing duplicate code means creating an abstraction that can handle this set of different things with just one function/module/class.

//BAD
function showDeveloperList(developers) {
developers.forEach(developer => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach(manager => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}//GOOD
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}

Do Not Pollute The Globals

Polluting globals is a bad practice in JavaScript because you could clash with another library and the user of your API would be none-the-wiser until they get an exception in production. For example, if you wanted to extend JavaScript’s native Array method to have a diff method that would show the difference between two arrays. You can write your method to Array.prototype , but it can clash with another library that had tried to call the same diff method to implement another feature.

This is why it would be much better to just use ES2015/ES6 classes and simply extend the Array global.

//BAD
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};//GOOD
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}

Always “use strict” On

Chances are that if you are using any library/framework or compiler for your Javascript, “use strict” is on but just in case you are not, remember to add it to the file and to the functions. It will make sure you get errors that would happen silently if don’t include it.

Use Function expressions instead of Function Declarations

Unless you want to take advantage of Function behavior and properties, prefer function expressions. Function declarations are hoisted and although it can be useful sometimes, avoid them as they introduce weird behavior to the code and it is not always obvious what’s happening. Try to make it clear where the function you are using comes from and they come before you use them to avoid weird access.

Stop using “var”!

Declarations with “var” are also hoisted which makes var declarations be accessible before where the declaration happened which is weird, non-obvious behavior.

Use “const” and immutability as much as possible

Prefer immutability as much as possible. Constantly changing data and passing it around can make it hard to track bugs and the changes itself. Work on data copies and avoid side effects.

Prefer Pure Functions

Continuing on the side effect note, ensure your functions are not changing data they are called with or data in the scope where they are created.

Prefer Class over Constructor Functions

Although the constructor function allows you to do some very nice stuff, if you find yourself reaching out for its prototype is a sign you need to use “class” which are supported pretty much anywhere. It is cleaner and something people are more likely to understand.

Use “destructuring”

Destructuring is elegant and makes it more obvious what you need from array and objects and also gives you the opportunity to rename things to help give more sense to your code.

Only work with data you need

Like the above examples, destructuring is a good way to extract the data you need to do the job but, also make it a habit to only call methods and functions with the things they need. This also goes to the data coming from the API. Extract and cleanup only the data you need before storing or doing anything to it.

Always use “===”

The triple equal checks for value and type and it is something you always want to do. Make it a habit to always triple check and avoid undesirable effects.

Avoid Global Variables

Avoiding creating things in global objects unless you are creating a library/framework. Global property names may collide with third parties or something a colleague also introduced and are hard to debug.

Wrap loose declarations in blocks

You can avoid name clash and loose temporary declarations access by wrapping a quickly logic in its own scope.

Organize your declarations

Be consistent with the way you declare things. Put all your declarations on top starting with the constants down to the variables. Make constants all uppercase to indicate they are constants which will prevent devs from trying to change them.

Don’t initialize things with “undefined”

Something is “undefined” when it lacks value. Let’s agree that assigning “no value” as a “value” for something is a pretty weird concept right? Since Javascript already makes things “undefined” how can you tell whether something is undefined because of you or Javascript? It makes it hard to debug why things are “undefined” so prefer setting things to “null” instead.

Always initialize your declarations

For the same reason, you should not give “undefined” as a value to declarations, you should not leave them without a value because they are “undefined” by default.

Lint your code and have a consistent style

Linting your code is the best way to ensure a consistent look and feel of your code and make sure people don’t do weird things to it as well. It puts everyone on the same page.

Use Typescript

Typescript can help you a lot in delivering better code. It will need some getting used to if you never tried a type system but it pays off in the long run.

Functions and methods should do one thing only

It is easy to get carried away with adding extra stuff to function while you are at it and the best way to find out whether a function is doing too much is by looking at its name. The name should tell what the function does and anything unrelated should go.

Don’t be lazy when naming things

Always put some effort into naming things. If it is hard to name you probably gave it extra responsibility or do not understand what it is. Give it at least a 3 letter name with meaning.

Avoid unnecessary declarations

Some declaration can be avoided altogether so only declare when it is strictly necessary. Too many declarations may hint at a lack of proper code design or declaration consideration

Use default values when posible

Having defaults is more elegant than throwing errors because something was not provided. If you really want to catch not provided values you can check my article on 25 javascript solution where I share a way to make things required that throws error if no value provided.

Always have a default case for switch statements

Don’t leave your switch statements without a default case because something can go wrong and you want to make sure you catch it.

Never use “eval”

Never! It is not necessary.

Avoid the “new” keyword

Except for class and constructor functions instancing, you should never use the “new” keyword for anything else. They can slow compilers down. Javascript is so weird that even leaving out the “new” keyword sometimes things just work.

Add meaningful comments for nonobvious things

Only add comments when you did something not common, weird, or requires context to be understood. Also, add comments to things that are a hack or may require improvements/fixing later on so the next person knows why. Add comments in your third parties’ modules and modules in your codebase to explain the architecture and the intention behind things.

Keep ternaries simple

Worst case scenario you have two nested ternaries. Anything longer should be an if statement or switch for readability and easy to debug reasons.

Simplify with optional chaining

Get rid of those nested checks and use the “?” Operator.

Prefer promises over callbacks

Promises are easy to use and anything with a callback can be “promisified”. Callbacks are synchronous and with promises and async…await you get to do things asynchronous which help speed up your code, especially because Javascript is single-threaded.

For loops > .forEach sometimes

Don’t change things into an array just so you can “.forEach” it. You are adding extra process to a slow alternative. For loops are faster and allows you to use the “continue” and “break” keywords to control the looping.

“for…in” and “for…of”

The for-in and for-of loops are very powerful ways to loop. The “for-of” loop lets you go over the values of the array, strings, Map, Set, etc. No need to change something into an array to use .forEach. I would avoid the “for-in” for looping as it is the slowest one and iterates over prototype keys.

Optimize for loops

If you ever need to make for-loops faster try storing the properties of “length” for example and avoid accessing it on every iteration. Check this amazing article explaining loop optimization in more detail.

Always “try…catch” JSON methods

Don’t trust things passed to JSON methods “.stringify” and “.parse”. Try to catch them to make sure they don’t fail and break your code.

Prefer template strings

It is that simple. Template strings allow you to inject values into the string and they keep the format that can come in handy.

Avoid nesting or chaining loops

When you chain iteration method or nest loops you are increasing the complexity of the code which may slow things down later on or as your data grows. Even though some operations may require it, always assess your looping strategy to ensure you don’t have unnecessary loops or loops that can be combined together.

Avoid Weird Unreadable hacks

They are all over the internet because people find them “cool”. They are usually weird, non-conventional, and non-obvious when you look at them. It is always best to follow the guidelines of the tool you are using to ensure proper performance. Hacking should be that last alternative.

Prefer the “rest” operator over “arguments”

The “rest” operator will work with arrow functions where “arguments” are not available. Stick to one way to access your function arguments.

Prefer “globalThis” for global access

Let the Javascript handle the rest and make sure that your code will work whether it is inside a Web Worker or Backend Node.

Understand Javascript but Build with Libraries and Frameworks

I recommend investing time in understanding the Javascript language itself but build with powerful tools like React and Angular to avoid common mistakes. Make sure you follow their guidelines since these tools already guard for common mistakes and employ best practices.

Add semicolons, always!

You may be surprised to find out that you can get away with not putting a semicolon in the Javascript code. Know that the compiler adds them and tools like Babel may easily misread your code and cause a bug to make to production. Always add semicolons!

Readable > Performance unless you need Performance

There are ways to get more performance by doing things that are often hard to read but unless you are desperate for performance at the code level (which is rare), make it readable.

Be careful with “Truthy” and “Falsy” checks

Don’t rely on the “truthy” and “falsy” checks since you can easily introduce bugs to your code. Try to be specific in your checks as unexpected things may pass as a truthy check.

Prefer Ternary over logical “||” and “&&” checks

The “or” and “and” operators coerce values to “true” and “false” which may result in undesired results. Also, don’t rely on it to do weird logical condition checks as they are not readable and easy to understand.

Watch out for “undefined” and “null” with the “??” operator

The nullish coalescing operator makes sure that null and undefined values are not picked and it is perfect for cases where you want to ensure that there is a value or fallback to a default value.

Be careful with automatic type conversions

This is probably another reason to try Typescript as Javascript does an automatic type conversion on the fly which may not be what you are expecting. “Truthy” values become “true” and “Falsy” values become “false”. Doing math between number and string may actually work or result in a string concatenation. Numbers almost always turn “Falsy” values into “zero” and “Truthy” into “one”.

Never trust data you don’t create

Whenever you are dealing with data coming from a user or from an API you don’t know, make sure it is of the right type and in a format that you can work with before you do any operation on it.

Use regex when extracting and looking for things in Strings

Regex is super powerful and fun. Avoid weird manipulation like looking for indexes and grabbing things. Regex allows you to look for complex patterns and

IIFE and small utility libraries

IIFE is an excellent way to execute things as early as possible which you can take advantage of to set up some stuff before the rest of the code starts running. You can also use it to initialize small libraries with a simple API that allows you to encapsulate some complex logic and expose an object you can use to interact with similar to how jQuery is built.

Avoid repeating yourself with utilities

Always turn things you do repeatedly into small generic functions that you can reuse later on. As a developer, you should not be repeating things and small functions make them easy to test and reuse.

Don’t take advantage of weird Javascript “features”

Things like updating array length property, using the “with” keyword, void keyword, updating native Object prototypes like Date, Array, Object, etc. Others like passing a string to setTimeout and setInterval. Just because the language allows you to, does not mean you should.

Add Unit Tests

As a developer, I often found bugs when I started adding unit tests. Tests are the ultimate way to ensure that your code as error-free as possible. Jest is an excellent option to start with but there are others out there that are also as simple to use.

Although this is a vast topic, I have limited it to only variables and functions to keep the post short. I have only included a fraction of what you can do to write clean code. I highly suggest you read “Clean Coder” by Robert C. Martin.

Become a world’s no 1 full-stack web developer. That’s why I am learning and mastering web development. I will not stop until I become the Web Development Hero.