DEV Community

Cover image for 7 Tips for Clean Code in JavaScript You Should Know
Kai
Kai

Posted on • Edited on • Originally published at kais.blog

7 Tips for Clean Code in JavaScript You Should Know

This post was originally published at kais.blog.

Let's move your learning forward together! Follow me on Twitter for your daily dose of developer tips. Thanks for reading my content!


As a developer, you'll spend much more time reading code than writing it. That's why it's important to write code that's quick to grasp and easy to maintain. In this post, I want to introduce you to 7 tips that will help you create a clean and robust codebase.

Please note that these are opinionated guidelines. Some of you may not agree with me. In general, these tips will not be universal. Also, this list is not exhaustive at all.

1. Use meaningful names

There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton

Yeah, naming things is hard. However, meaningless names will trigger chaos in the long run. Whenever you have to choose a name - be it for a variable, a class, a function or anything else - please use meaningful names. The name should tell you the purpose and context.

❌ Bad

function su(e, pw) {
  const u = new U(e, pw);
  // What the §*{$ is this?
}
Enter fullscreen mode Exit fullscreen mode

✔️ Good

function signup(email, password) {
  const user = new User(email, password);
  // Ah, now I understand!
}
Enter fullscreen mode Exit fullscreen mode

2. Replace magic numbers with constants

What is a magic number? A magic number is a hard coded numeric value. It's an anti-pattern and obscures the developer's intent. Thus, it should be replaced with a constant that describes its purpose. See, you can instantly apply your knowledge from the first tip.

❌ Bad

for (let i = 0; i < 52; i++) {
  // ...um, why again do we use `52` here?
}
Enter fullscreen mode Exit fullscreen mode

✔️ Good

const DECK_SIZE = 52;

for (let i = 0; i < DECK_SIZE; i++) {
  // It's about a deck of playing cards.
}
Enter fullscreen mode Exit fullscreen mode

Here, you may ask why i = 0 is okay. Well, I'd count this as acceptable use. The intent here is clear - using i and initializing it with 0 is widely known among developers.

3. Do not use boolean flags to determine behavior

Often, you encounter a function that has two very similar behaviors. To switch between those, you might be tempted to simply add a boolean flag. However, this makes your code less readable and harder to understand. Try to split the function into two functions without the flag instead.

❌ Bad

function loadSchema(schema, sync = false) {
  //
}

// One eternity later…

loadSchema("", true);
// Wait, what is `true` here? Sync? Async? 
// Something else? I'm so forgetful.
Enter fullscreen mode Exit fullscreen mode

✔️ Good

function loadSchema(schema) {
  //
}

function loadSchemaSync(schema) {
  //
}

// One eternity later…

loadSchemaSync("");
// Ah, it's the synchronous variant.
Enter fullscreen mode Exit fullscreen mode

4. Reduce nesting in your code

Nesting makes code harder to read and especially harder to understand. With some simple tricks you can reduce nesting to a minimum.

❌ Bad

async function handle(request) {
  if (request.user) {
    if (request.user.can("CREATE_POST")) {
      // Wow, this is deep.
      // Handle request here.
    } else {
      // User is not authorized.
      return new Response({ status: 403 });
    }
  } else {
    // User is not authenticated.
    return new Response({ status: 401 });
  }
}
Enter fullscreen mode Exit fullscreen mode

✔️ Good

async function handle(request) {
  if (!request.user) {
    // User is not authenticated.
    return new Response({ status: 401 });
  }

  if (!request.user.can("CREATE_POST")) {
    // User is not authorized.
    return new Response({ status: 403 });
  }

  // We can safely assume the user
  // is authenticated and authorized.
  // Handle request here.
}
Enter fullscreen mode Exit fullscreen mode

5. Make use of newer language features

JavaScript is constantly changing. This brings you awesome new features that can improve your codebase. You can use destructuring, classes, the async-await syntax, the numeric separator and much more. My favorites are probably the spread-Operator (...), the optional-chaining operator (?.) and nullish-coalescing (??).

❌ Bad

// Assigning a default value should be easier...
const port = typeof config.port !== "undefined" ? config.port : 3000;

// Did I mess up? It's nine zeros, right?
const oneBillion = 1000000000;

// Deep properties and nesting...urghs
if (user.team) {
  if (user.team.subscription) {
    if (user.team.subscription.invoices) {
      //
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

✔️ Good

// Let's use nullish-coalescing (`??`).
const port = config.port ?? 3000;

// The numeric separator makes it easy to tell.
const oneBillion = 1_000_000_000;

// Here, we can use optional-chaining.
if (user.team?.subscription?.invoices) {
  //
}
Enter fullscreen mode Exit fullscreen mode

Note that you cannot use optional-chaining on a non-existent root object. So if user could be undefined, we'd have to check with something like typeof user !== "undefined" first.

6. Make your code easy to refactor

Refactoring is the restructuring of your code without changing the observable behavior. To make this easy, you should consider writing automated tests. Therefore, you can use testing frameworks like Jest. If you are using automated tests you can verify that your code is behaving like you'd expect.

Then, you are ready for refactoring. You can change your code however you want. As long as your tests are passing, everything is fine. This should enable you to be confident about your codebase. No more fear that you are accidentally breaking something.

Unfortunately, setting up a testing framework like Jest is beyond the scope of this article. If you want, I can create a post about testing (and refactoring) your JavaScript code.

7. Use ESLint

This is the final tip of this post. Use this awesome tool called ESLint. It's free and easy to use and surely will improve your codebase. It detects and fixes common problems. Also, you can install useful presets and plugins to detect even more and reformat your code according to a style guide.

I use ESLint with plugins for standard and prettier. Besides, if I'm working with Vue, I'll add eslint-plugin-vue. Unfortunately, explaining the installation and configuration of ESLint is also beyond the scope of this article. Tell me, if you'd like to hear more about this.

Bonus: Consider using TypeScript

If you've read any of my posts in the past, you might know that I'm using TypeScript, a superset of JavaScript. It's basically JavaScript on steroids and helps you writing more robust and maintainable code. If you are still undecided, take a look at these 6 Reasons Why You Should Learn TypeScript in 2021 .

Conclusion

There's so much more you can do to create a clean and maintainable codebase. With my post, you should have a overview about small things you can do to improve your code. In the future, I'll publish more content to make you a better programmer.


Let's move your learning forward together! Follow me on Twitter for your daily dose of developer tips. Thanks for reading my content!

This post was originally published at kais.blog.

Top comments (12)

Collapse
 
havespacesuit profile image
Eric Sundquist

Sometimes it is easier to use a boolean flag as a function argument, if splitting the function would result in two almost identical functions. But rather than pass in a boolean, I like to give a unioned string option in TypeScript which is descriptive of what the flag means:

function loadSchema(schema: Schema, sync: 'sync' | 'async') {
 // 
} 

// One eterminty later...

loadSchema("...", 'sync')
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kais_blog profile image
Kai

Yeah, nothing is set in stone. If you feel like this is appropriate, go for it. In my opinion it's much better than a boolen flag already. If you are using TypeScript this is a good alternative. However, for JavaScript I won't recommend this, as you could do the following:

loadSchema("", "fast")
Enter fullscreen mode Exit fullscreen mode

At first glance it's not obvious that "fast" is an invalid argument here.

Collapse
 
attraverso_dev profile image
attraverso

Thank you for sharing this knowledge. While I was reading I recognized a couple of mistakes/bad practices I've been seeing in the work I'm collaborating on these days... I'll make sure to try and correct them from now on!

Collapse
 
kais_blog profile image
Kai

Thanks!

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited
// Did I mess up? It's nine zeroes, right?
const oneBillion = 1000000000;
Enter fullscreen mode Exit fullscreen mode

An easier fix here:

const oneBillion = 1e12 // Long scale rules
Enter fullscreen mode Exit fullscreen mode

and that's not even a "new" language feature. And to answer the question: 12. It's 12 zeroes. You can literally read it as a number.

Collapse
 
kais_blog profile image
Kai • Edited

Thanks for your feedback. I really appreciate it. I'd like to add some thoughts to this, though:

First, one billion is 9 zeros in American and British English nowadays (short scale definition). This is sometimes confusing as it's 12 zeros in some other countries (long scale definition). For example, in Germany, we have Million (6 zeros), Milliarde (9 zeros) and Billion (12 zeros). As we have an english-speaking audience here, I've chosen to ignore my country's default definition and used 9 zeros. Also, you usually use English for code.

Second, you are right, you could use the e-notation. In this case, it would've been:

const oneBillion = 1e9; // short scale definition
Enter fullscreen mode Exit fullscreen mode

However, it's important to note that you are just adding zeros here. It's not so useful for other numbers.

Third, I feel like the numeric separator is still a very useful thing. As I said, the e-notation can be used, if you have to add zeros. Then, maybe it's easier. Nevertheless, the numeric separator has other uses as well:

const priceInCents = 490_95;

//  four-bit aggregations
const nibbles = 0b1011_1100_0011_1010;
Enter fullscreen mode Exit fullscreen mode

So, it might not be the right thing to use everytime. Yet, I'd like to highlight that nothing is set in stone and there are always exceptions and different opinions on how to structure and write clean code.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I totally agree that _ has its uses and can make code much more reasonable in cases where e-notation wouldn't do so. But for the specific case of having lots of 0s, I'd always prefer the e-notation, because you can just read the number of zeroes. Something like 1e32 would be difficult to count even with _-notation.

Thread Thread
 
kais_blog profile image
Kai

true. I agree. Maybe next time, I should choose a different example. Thanks for your input, though.

Collapse
 
vladi160 profile image
vladi160

Not a big deal and not important for the purpose of the article, just you can't use optional-chaining in a root developer.mozilla.org/en-US/docs/W...

Collapse
 
kais_blog profile image
Kai

Thanks for mentioning this. I've just tried to come up with a code example fast and have overlooked that in the process. I'll fix it in the article.

Collapse
 
patilrushikesh247 profile image
patilrushikesh247

thank you for sharing this its very useful content

Collapse
 
kais_blog profile image
Kai

Thanks. I'm glad I could help.