Javascript Clean Code Tips & Good Practices

Javascript Clean Code Tips & Good Practices

15 Tips I Follow For Writing Better Code

Featured on daily.dev
Apoorv Tyagi

Published on Aug 22, 2021

8 min read

Subscribe to my newsletter and never miss my upcoming articles

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) {
    //...
}

image.png

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

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...
  });
}

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.

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];
}

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:

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;
  }
}

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:

Did you find this article valuable?

Support Apoorv Tyagi by becoming a sponsor. Any amount is appreciated!

See recent sponsors |Β Learn more about Hashnode Sponsors
Β 
Share this