7 Tips for Clean Code in JavaScript You Should Know
Kai

Kai @kais_blog

Location:
Germany
Joined:
Dec 5, 2020

7 Tips for Clean Code in JavaScript You Should Know

Publish Date: Jan 5 '21
148 12

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.

Comments 12 total

  • attraverso
    attraversoJan 6, 2021

    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!

    • Kai
      KaiJan 8, 2021

      Thanks!

  • Eric Sundquist
    Eric SundquistJan 6, 2021

    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
    • Kai
      KaiJan 6, 2021

      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.

  • patilrushikesh247
    patilrushikesh247Jan 7, 2021

    thank you for sharing this its very useful content

    • Kai
      KaiJan 8, 2021

      Thanks. I'm glad I could help.

  • vladi160
    vladi160Jan 8, 2021

    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...

    • Kai
      KaiJan 8, 2021

      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.

  • 𒎏Wii 🏳️‍⚧️
    𒎏Wii 🏳️‍⚧️Jan 8, 2021
    // 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.

    • Kai
      KaiJan 8, 2021

      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.

      • 𒎏Wii 🏳️‍⚧️
        𒎏Wii 🏳️‍⚧️Jan 8, 2021

        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.

        • Kai
          KaiJan 8, 2021

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

Add comment