Code should be written in such a way that is self-explanatory, easy to understand, and easy to modify or extend for the new features. Because code is read more than it is written that's why so much emphasis is given to clean code.
The more readable our source code is:
- The easier it is to maintain
- The less time required to understand an implementation for a new developer
- The easier it is to discover what code can be reused
In this blog post, I will share some general clean coding principles that I've adopted over time as well as some JavaScript-specific clean code practices.
0. Naming
Don't turn naming into a riddle game. Name your variables and functions in a way that they reveal the intention behind why they were created in the first place.
This way they become searchable and easier to understand if let's say a new developer joins the team.
Only go for Shortening and abbreviating names when you want the next developer working on your code to guess what you were thinking about π
Bad π
let x = 10;
let y = new Date().getFullYear();
if (x > 30) {
//...
}
if (y - x >1990) {
//...
}
Good π
let userAge = 30;
let currentYear = new Date().getFullYear();
if (userAge > 30) {
//...
}
if (currentYear - userAge >1990) {
//...
}
Also, donβt add extra unnecessary letters to the variable or functions names.
Bad π
let nameValue;
function theProduct();
Good π
let name;
function product();
1. Conditionals
Avoid negative conditionals. Negatives are just a bit harder to understand than positives.
Bad π
if (!userExist(user)) {
//...
}
Good π
if (userExist(user)) {
//...
}
2. Functions should do one thing
The function should not have more than an average of 30 lines (excluding spaces and comments). The smaller the function the better it is to understand and refactor. Try making sure your function is either modifying or querying something but not both.
3. Use default arguments
Use default arguments instead of short-circuiting or conditionals.
Default arguments are often cleaner than short-circuiting. Remember that if you use them, your function will only provide default values for undefined arguments. Other falsy values such as '', "", false, null, 0, and NaN, will not be replaced by a default value.
Bad π
function getUserData(name) {
const userName = userName || "Patrick Collision";
// ...
}
Good π
function getUserData(name = "Patrick Collision") {
// ...
}
4. Single Level of Abstraction(SLA)
While writing any function, if you have more than one level of abstraction, your function is usually doing more than one thing. Dividing a bigger function into multiple functions leads to reusability and easier testing.
Functions should do one thing. They should do it well. They should do it only. β Robert C. Martin
Bad π
function checkSomething(statement) {
const REGEXES = [
// ...
];
const statements = statement.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
// ...
});
});
const names= [];
tokens.forEach(token => {
// lex...
});
names.forEach(node => {
// parse...
});
}
Good π
function checkSomething(statement) {
const tokens = tokenize(statement);
const syntaxTree = parse(tokens);
syntaxTree.forEach(node => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
tokens.push(/* ... */);
});
});
return tokens;
}
function parse(tokens) {
const syntaxTree = [];
tokens.forEach(token => {
syntaxTree.push(/* ... */);
});
return syntaxTree;
}
5. Don't ignore caught errors
Doing nothing with a caught error doesn't give you the ability to fix or react to that particular error.
Logging the error to the console (console.log) isn't much better as oftentimes it can get lost among other things printed to the console.
If you wrap any bit of code in a try/catch it means you think an error may occur there and therefore you should have a plan for when it occurs.
Bad π
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
Good π
try {
functionThatMightThrow();
} catch (error) {
notifyUserOfError(error);
reportErrorToService(error);
}
6. Minimize Comments
Only comment the part of the code that has business logic complexity.
Comments are not a requirement. Good code mostly documents itself.
Bad π
function hashing(data) {
// The hash
let hash = 0;
// Length of string
const length = data.length;
// Loop through every character in data
for (let i = 0; i < length; i++) {
// Get character code.
const char = data.charCodeAt(i);
// Make the hash
hash = (hash << 5) - hash + char;
// Convert to 32-bit integer
hash &= hash;
}
}
Good π
function hashing(data) {
let hash = 0;
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data.charCodeAt(i);
hash = (hash << 5) - hash + char;
// Convert to 32-bit integer
hash &= hash;
}
}
βRedundant comments are just places to collect lies and misinformation.β β Robert C. Martin
7. Remove commented code
Don't leave commented out code in your codebase, Version control exists for a reason. Leave old code in your history. If you ever need them back, pick them up from your git history.
Bad π
doSomething();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();
Good π
doSomething();
8. Import only what you need
Destructuring was introduced with ES6. It makes it possible to unpack values from arrays, or properties from objects, into distinct variables. You can use this for any kind of object or module.
For instance, if you only require to add()
and subtract()
function from another module:
Bad π
const calculate = require('./calculations')
calculate.add(4,2);
calculate.subtract(4,2);
Good π
const { add, subtract } = require('./calculations')
add(4,2);
subtract(4,2);
It makes sense to only import the functions you need to use in your file instead of the whole module, and then access the specific functions from it.
9. Keep Function arguments 3 or less (ideally)
Limiting the number of function parameters is really important because it makes testing your function easier. Having more than three parameters leads you to test tons of different cases with each separate argument.
1-3 arguments are the ideal case, anything above that should be avoided if possible.
Usually, if you have more than three arguments then your function is trying to do too much. Which ultimately leads to the violation of the SRP(Single Responsibility Principle).
10. Use array spreads to copy arrays.
Bad π
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
Good π
const itemsCopy = [...items];
11. Write linear code
Nested code is hard to understand. Always write the linear code as much as possible. It makes our code simple, clean, easy to read, and maintain, thus making developer life easier.
For Example, Using promises over callbacks can increase readability multiple times.
12. Use ESLint and Prettier
Always use ESLint and Prettier to enforce common coding styles across teams and developers.
Also try and use JavaScript's latest features to write code, like destructuring, spread operator, async-await, template literals, optional chaining, and more.
13. Use proper parentheses
When working with operators, enclose them in parentheses. The only exception is the standard arithmetic operators: +, -, and ** since their precedence is broadly understood. It is highly recommended to enclose /, *, and % in parentheses because their precedence can be ambiguous when they are used together.
This improves readability and clarifies the developerβs intention.
Bad π
const foo = a && b < 0 || c > 0 || d + 1 === 0;
if (a || b && c) {
return d;
}
Good π
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
if (a || (b && c)) {
return d;
}
Make sure your code doesn't lead to situations like this:
14. Return early from functions
To avoid deep nesting of if-statements, always return a function's value as early as possible.
Bad π
function isPercentage(val) {
if (val >= 0) {
if (val < 100) {
return true;
} else {
return false;
}
} else {
return false;
}
}
Good π
function isPercentage(val) {
if (val < 0) {
return false;
}
if (val > 100) {
return false;
}
return true;
}
This particular example can even improve further:
function isPercentage(val) {
var isInRange = (val >= 0 && val <= 100);
return isInRange;
}
Similarly, the same thing can be applied to Loops as well.
Looping over large cycles can surely consume a lot of time. That is why you should always try to break out of a loop as early as possible.
Conclusion
Thereβs a saying in the development community that you should always write your code like the next developer that comes after you is a serial killer.
Following this rule, I have shared 15 tips here that can (probably) save you from your fellow developers when they will look into your code.
If you find any updates or corrections to improve these 15 tips or want to add one of your own that you think can be helpful, please feel free to share them in the comments.
For further reading I would highly suggest you go through these 3 resources:
Starting out in web development?? π»
Checkout βΆ HTML To React: The Ultimate Guide
This ebook is a comprehensive guide that teaches you everything you need to know to be a web developer through a ton of easy-to-understand examples and proven roadmaps
It contains π
β Straight to the point explanations
β Simple code examples
β 50+ Interesting project ideas
β 3 Checklists of secret resources
β A Bonus Interview prep
You can even check out a free sample from this book
and here's the link with 60% off on the original price on the complete book set β¬
Top comments (9)
Nice list, quite complete.
Just one thing, in the copy array example, the spread operator does a shallow-copy. It will copy things like strings, numbers, but not objects or other arrays:
This can lead to some weird behaviour, and some tricky bugs. I've had some problems with this in the past, and it took a while to fix. Just a thing to notice!!
But it's not a big deal, and a simple fix!
Oh this can lead to some problems, yes. I didn't know that. Thanks for sharing this π
Nice writeup, thank you very much.
About default arguments it should be mentioned, that the "bad" solution may also cause unexpected errors:
val may be empty or zero to get the second argument of ||
Thanks.
yeah that's a good one to know as wellπ
Very nice article, thanks. I will change the name
isUserExists()
the function name doesn't make sense. It should beisUserValid()
oruserExists()
Thanks and yeah we can do that. The "is" is kinda irrelevant!
I have list of Modern Syntax For JavaScript Best Practice For Beginners
OMG, thanks for share this.
I can repost this post in my blog in portuguese (i going to develop it)?
I refer you and this post with owner.
Sure man go for it!
Also once you're done done, share me the link as well :)