DEV Community

Cover image for Javascript Clean Code Tips & Good Practices
Apoorv Tyagi
Apoorv Tyagi

Posted on • Updated on • Originally published at apoorvtyagi.tech

Javascript Clean Code Tips & Good Practices

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) {
    //...
}
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

let userAge = 30;

let currentYear = new Date().getFullYear();

if (userAge > 30) {
    //...
}

if (currentYear - userAge >1990) {
    //...
}
Enter fullscreen mode Exit fullscreen mode

image.png

Also, don’t add extra unnecessary letters to the variable or functions names.

Bad πŸ‘Ž

let nameValue;
function theProduct();
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

let name;
function product();
Enter fullscreen mode Exit fullscreen mode

1. Conditionals

Avoid negative conditionals. Negatives are just a bit harder to understand than positives.

Bad πŸ‘Ž

if (!userExist(user)) {
  //...
}
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

if (userExist(user)) {
  //...
}
Enter fullscreen mode Exit fullscreen mode

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";
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

function getUserData(name = "Patrick Collision") {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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

image.png

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...
  });
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

try {
  functionThatMightThrow();
} catch (error) {
  notifyUserOfError(error);   
  reportErrorToService(error);   
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

β€œ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();
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

doSomething();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

const { add, subtract } = require('./calculations')

add(4,2);
subtract(4,2);
Enter fullscreen mode Exit fullscreen mode

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.

image.png

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];
}
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

const itemsCopy = [...items];
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

const foo = (a && b < 0) || c > 0 || (d + 1 === 0);

if (a || (b && c)) {
  return d;
}
Enter fullscreen mode Exit fullscreen mode

Make sure your code doesn't lead to situations like this:

Bad.png

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Good πŸ‘

function isPercentage(val) {
  if (val < 0) {
    return false;
  }

  if (val > 100) {
    return false;
  }

  return true;
}
Enter fullscreen mode Exit fullscreen mode

This particular example can even improve further:

function isPercentage(val) {
  var isInRange = (val >= 0 && val <= 100);
  return isInRange;
}
Enter fullscreen mode Exit fullscreen mode

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 ⬇

eBook.png

Top comments (9)

Collapse
 
nombrekeff profile image
Keff

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:

let obj1 = { name: 'test' };
let obj2 = { name: 'test2' };

let array = [obj1, obj2];
let copy = [...array];

array[0] === copy[0] // true

// Or if you modify copy, it will affect the original items in `array` 
copy[0].name = 'Other name';

array[0].name // 'Other name'

Enter fullscreen mode Exit fullscreen mode

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!

Collapse
 
apoorvtyagi profile image
Apoorv Tyagi

Oh this can lead to some problems, yes. I didn't know that. Thanks for sharing this πŸ‘

Collapse
 
efpage profile image
Eckehard

Nice writeup, thank you very much.

About default arguments it should be mentioned, that the "bad" solution may also cause unexpected errors:

    function getValue(val) {
      const myVal = val || 1;
      console.log(myVal)
    }
    getValue(2) // => 2
    getValue(0) // => 1
Enter fullscreen mode Exit fullscreen mode

val may be empty or zero to get the second argument of ||

Collapse
 
apoorvtyagi profile image
Apoorv Tyagi

Thanks.

yeah that's a good one to know as wellπŸ‘

Collapse
 
jcubic profile image
Jakub T. Jankiewicz • Edited

Very nice article, thanks. I will change the name isUserExists() the function name doesn't make sense. It should be isUserValid() or userExists()

Collapse
 
apoorvtyagi profile image
Apoorv Tyagi

Thanks and yeah we can do that. The "is" is kinda irrelevant!

Collapse
 
stuffsuggested profile image
niksin
Collapse
 
jrmarqueshd profile image
Junior Marques • Edited

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.

Collapse
 
apoorvtyagi profile image
Apoorv Tyagi

Sure man go for it!

Also once you're done done, share me the link as well :)