Skip to main content

Command Palette

Search for a command to run...

5 Tips to Write Clean Code in JavaScript

Write cleaner JavaScript with these 5 principles

Published
4 min read
5 Tips to Write Clean Code in JavaScript
S

Sr. Software Engineer @ Spotify | Tech Blogger 💻 | Professional Nerd 🤓 | Musician 🎸

As you become more experienced with JavaScript there are tips and principles on how to write clean code that you should know.

The craft of software engineering is a relatively new one. The craft of medicine has existed for thousands of years while software engineering has only been around since the beginning of the 1960s. We are all still learning how to write clean code. There are some principles though that are widely accepted by the community as best practices.

There are many principles for writing clean code, but I decided to focus on the five most important. These tips are recommendations and not strict rules. Practice these principles and your code will be cleaner!

Why Clean Code Is Important

As a beginner, your focus is not necessarily on writing clean code. Your focus should be on getting the code working and your tests passing. As you become more experienced you can start focusing on writing clean code.

In this article, I define clean code as:

  • Easy to read
  • Easy to understand

Clean code will help you get up-to-speed with and maintain a codebase. It's always worth the effort of making your code cleaner, even if it takes a longer time to write!

Write Clean Code in JavaScript

JavaScript, like any other language, has its special quirks. These tips are not necessarily limited to JavaScript and can be used for many other languages.

Extract Magic Numbers to Constants

"Magic numbers" refer to numbers that are used directly in the code, without any context. They lack meaning and should be extracted to constants with a meaningful variable name.

Bad:

const isOldEnough = (person) => {
  // What does 100 refer to? This is a so-called "magic number"
  return person.getAge() >= 100;
}

Good:

const AGE_REQUIREMENT = 100; // Now we know what 100 refers to

const isOldEnough = (person) => {
  return person.getAge() >= AGE_REQUIREMENT;
}

Avoid Boolean Function Arguments

Boolean function arguments are a typical "code smell" (breaking fundamental programming standards). This is because they lack meaning. And it indicates that your function does more than one thing, which you should always avoid!

Bad:

const validateCreature = (creature, isHuman) => {
  if (isHuman) {
    // ...
  } else {
    // ...
  }
}

Good:

const validatePerson = (person) => {
  // ...
}

const validateCreature = (creature) => {
  // ...
}

Encapsulate Conditionals

Long conditional statements are hard to read because you have to keep all of them in your head. By encapsulating them into a function or a variable your code will be easier to reason about.

Bad:

if (
  person.getAge() > 30 &&
  person.getName() === "simon" &&
  person.getOrigin() === "sweden"
) {
  // ...
}

Good:

const isSimon =
  person.getAge() > 30 &&
  person.getName() === "simon" &&
  person.getOrigin() === "sweden";

if (isSimon) {
  // ...
}

// OR use a function

const isSimon = (person) => {
  return (
    person.getAge() > 30 &&
    person.getName() === "simon" &&
    person.getOrigin() === "sweden"
  );
};

if (isSimon(person)) {
  // ...
}

Avoid Negative Conditionals

Negative conditionals ("double negation") add an extra condition to your brain when you're reading the code. You would not say "I'm not not sleepy". You would say "I'm sleepy". The same practice applies to code!

Bad:

const isCreatureNotHuman = (creature) => {
  // ...
}

if (!isCreatureNotHuman(creature)) {
  // ...
}

Good:

const isCreatureHuman = (creature) => {
  // ...
}

if (isCreatureHuman(creature)) {
  // ...
}

Don't Use If Statements

This might sound crazy, but hear me out! If statements are easy to understand but do not scale. As soon as you have more than one or two if statements in the same function the code easily becomes hard to reason about.

Instead, use a switch statement or, if possible, a data structure like Array, Set, or Map.

Bad:

const isMammal = (creature) => {
  if (creature === "human") {
    return true;
  } else if (creature === "dog") {
    return true;
  } else if (creature === "cat") {
    return true;
  }
  // ...

  return false;
}

Good:

const isMammal = (creature) => {
  switch (creature) {
    case "human":
    case "dog":
    case "cat":
    // ...
      return true;
    default:
      return false;
  }
}

// OR even better, use a data structure

const isMammal = (creature) => {
  const mammals = ["human", "dog", "cat", /* ... */];
  return mammals.includes(creature);
}

Conclusion

These are my five most important principles for writing clean code in JavaScript. Practice makes perfect, and the same goes for writing code. If you already follow these principles today - keep it up! I'm sure you've practiced writing code a lot. If not - don't be discouraged! We all start somewhere 🙂

To summarise:

  • Extract Magic Numbers to Constants
  • Avoid Boolean Function Arguments
  • Encapsulate Conditionals
  • Avoid Negative Conditionals
  • Don't Use If Statements

Connect with me on Twitter, LinkedIn, or GitHub

R

nice tips

J
JD Lien3y ago

Great article, Simon!

I am finding it hard to universally agree with the notion of not making functions with boolean (flag) arguments. If you had to make a different function for every variation of a method, wouldn’t you often end up with multiple functions with a lot of duplicate or similar code in them?

I suppose the obvious solution to this is to break such functions up into smaller methods, but then in some cases wouldn’t you end up with several methods where one would do that just accepts a few flags?

Would you consider it acceptable to have a parent function with flags and some simple methods to call the parent just for readability’s sake?

If you can give further clarifications or examples, that would be great!

1
S

Hi JD! And thanks!

I'd argue that boolean parameters should never be used. Of course, everything is contextual :)

It all boils down to having the code readable and easy to reason about. Would you not prefer three small functions that do one thing and one thing only, over one function with two boolean parameters?

It might be tempting to write that bigger function. But then later someone else might want to add a flag to the function and then you have three flags. Refactoring this will be a pain. And when you read the code you need to keep in mind what all these booleans represent.

For languages with named arguments (such as Python and Scala) it makes more sense to use flags, assuming the linter forces you to always name boolean arguments.

For some occasions, it might make sense to do something like this: https://gist.github.com/simeg/250fc0e300b75e4bd9fbc6161e00183f

Notice the difference in visibility. Although I would advise against this if you're writing new code. This is a code smell. For this case, polymorphism should be used.

Thanks for asking this! :) I hope I make sense. If you have specific examples to discuss please share!

J
JD Lien3y ago

Simon Egersand 🎈

Thank you for the thoughtful response! I don’t have a specific example in mind now, but I guess the kind of scenario where this may make sense to me (to use a boolean argument) is the sort of thing where you have a lot of setup to do, but then you might want to go down a couple of roads with what it returned.

I guess the “setup” work could be extracted to another function. I know the Uncle Bob Clean Code approach favours many small, single purpose functions over few large, monolithic ones.

I’ve been writing lots of functions with all kinds of boolean arguments for a long time (especially in ColdFusion custom tags), and I only recently heard of this concept that such arguments are a code smell. I guess it’s time for me to have a second look at how I write these sorts of things!

J
JD Lien3y ago

Simon Egersand 🎈

I just found this real function today. It grabs a user's photo from an API, but if you wanted to force the photo to update when it normally wouldn't, I created a boolean option for that.

public function saveUserPhoto(User $user, bool $forceUpdate = false): void
{
    if ($this->userHasUpdatedPhoto($user)) return;

    if (!$this->userHasPhoto($user) || $forceUpdate) {
        try {
            $response = $this->graphServiceClient->usersById($user->id)->photo()->content()->get();

            $photoContent = $response->wait();

            $this->savePhoto($user->id, $photoContent);
        } catch (ApiException $ex) {
            echo $ex->getMessage();
        }
    }
}

I just refactored the code into this - the drawback is that I had to check existing implementations to ensure they were calling the correct function, but in the end the code should be cleaner to read and the complexity is probably about the same in exchange for a couple extra lines of code in the file.

    /** Process and save a single user's photo given a userId. */
    public function saveUserPhoto(User $user): void
    {
        if ($this->userHasUpdatedPhoto($user)) return;

        try {
            // Get the photo from the Graph API
            $response = $this->graphServiceClient->usersById($user->id)->photo()->content()->get();

            $photoContent = $response->wait();

            $this->savePhoto($user->id, $photoContent);
        } catch (ApiException $ex) {
            echo $ex->getMessage();
        }
    }

    /** Process and save a single user's photo unless they already have a photo. */
    public function saveUserPhotoIfNoPhoto(User $user): void
    {
        if ($this->userHasPhoto($user)) return;

        $this->saveUserPhoto($user);
    }