We code the web

Improving code quality using ESLint

ESLint the Javascript linter of choice today. But it can do more than just checking semicolons and quotes. In this article we’ll go over how to configure ESLint to really help improve your code.

Getting started with ESLint

Although this article is not about the basics of ESLint, I will take a moment to show you how to get started. First of all install ESLint globally. Make sure you have Node.js on your machine and run the following command from the terminal:

npm install eslint -g

That’s it, ESLint is installed. Now go to any folder you want to work from and create a file called .eslintrc. Put the following configuration in there:

{
  "extends": "eslint:recommended",
  "parserOptions": {
    "ecmaVersion": 2016
  }
}

We just use the recommended ESLint rules for now. Now create a Javascript file called lint-me.js in the same folder and put the following code in it:

function shout(message) {
  return message.toUpperCase() + '!!!';
}

The ecmaVersion setting is neccesary for ESLint to recognize ES2016 syntax (you do not need the ESLint Babel plugin anymore). I always use ES2016 or higher in my blog posts and examples to promote progression in the Javascript world.

Now run ESLint using the following command:

eslint ./lint-me.js

You should see one error saying: “‘shout’ is defined but never used”. ESLint’s recommended rules check for most common mistakes. Of course creating a function but never using it is a waste! Try to fix it by adding: shout('Wecodetheweb is awesome');. Now run ESLint again, it will give no errors anymore. That’s it!

When you are just starting out, start by extending the recommended rules like we just did. Why? Because you will get lost in all the possible rules if you try to select your own set.

If the config file is empty, no rules will be applied. Only syntax errors will make ESLint error. If you extend a set of rules, all rules you define yourself will override those from the set.

The recommended rules catch the most common errors and bad practices in your code. Things like unknown variables being used, unreachable code or duplicate keys on objects will make ESLint error. Stuff that no developer wants! All rules that have a checkmark listed here are included in the recommended set.

Reducing complexity

Most people start using linters because they want all their team members to use the same style, like single quotes or two spaces of indentation. Although that is certainly useful and you should probably do that, in this article we’ll focus on improving actual code quality. Let’s start by reducing ‘cyclomatic complexity’.

Cyclomatic complexity is measured by the amount of branches a piece of code (in our case, a single function) has. A branch is a single and unique route you can take trough a piece code. High complexity is bad because it makes code hard to understand and maintain, as a result it is easy for bugs slip though.

Consider the following example:

function isHappy(user) {
  if (user.happiness > 6) {
    return true;
  }

  return false;
}

This function has a cyclomatic complexity of 2, because there are two possible routes the execution can take. Two is a nice and low complexity level. Let’s configure ESLint to keep it that way. 👍

{
  "parserOptions": {
    "ecmaVersion": 2016
  },
  "rules": {
    "complexity": [2, 5]
  }
}

The first value ‘2’ means this rule should trigger an error. The default value for complexity is 20 (!) which is way too high. Therefore we pass it the value 5. Now when a function has a higher complexity than five, it will trigger an error.

Somewhere between 4 and 6 is a nice value for complexity. Just try it out, take a complex piece of code from your existing codebase and run it through ESLint with this complexity setting, happy refactoring! 😎

Reducing the amount of statements

Functions should be short and concise. This makes them easy to read, maintain and reduces bugs.

function isHealthy(player, config) {
  const healthPercentage = player.health * 100;
  const isAboveInjuryLimit = healthPercentage > config.injuryLimit;
  const isSick = player.diseases.length > 0;

  return !isAboveInjuryLimit && !isSick;
}

The above function has 4 statements (which is fine 😇). More statements means more lines of code, make sure this is true by configuring the max statements per line rule. We want to keep our functions small like this. Let’s configure ESLint to check this for us:

{
  "parserOptions": {
    "ecmaVersion": 2016
  },
  "rules": {
    "complexity": [2, 5],
    "max-statements": [2, 7],
    "max-statements-per-line": [2, {
      "max": 1
    }]
  }
}

Somewhere around 7 is a nice value for max statements, you could go even lower. Max statements per line should be one, this makes statements easy to detect.

Reducing nesting

Remember callback hell? 🔥

doSomethingAsync(result => {
  doSomethingElse(result.thing, err => {
    if (!err) {
      if (result.success) {
        if (true) {
          doLastAsyncThing(() => {
            console.log(result);
          });
        }
      }
    }
  });
});

Don’t do this! You can extract the parts into functions or use something like Promises to prevent this. But let’s make sure this does not happen anymore using two more rules:

{
  "parserOptions": {
    "ecmaVersion": 2016
  },
  "rules": {
    "complexity": [2, 5],
    "max-statements": [2, 7],
    "max-statements-per-line": [2, {
      "max": 1
    }],
    "max-nested-callbacks": [2, 2],
    "max-depth": [2, {
      "max": 2
    }]
  }
}

This will make sure we never nest blocks and callbacks more then two levels deep, saving us from complex, hard to read and debug, tree-like code. 🎄

Other rules

These were some important rules that can help you write better code. But this is just the tip of the iceberg. Some of my favorite rules are:

  • "eqeqeq": 2 - Makes sure you never use == or != to check equality, because you know, it couses trouble.
  • "no-eval": 2 - We all know using eval is a generally bad idea…
  • "no-var": 2, "prefer-const": 2 - When using ES2015 or above (which you should if you can). This rule encourages you to never use var‘s and use const‘s instead of let‘s where possible, this forces you to think twice before reassigning a variable and not use the inferior function scoped var.
  • "max-lines": [2, 90] - Limits the maximum lines per file. It’s not that important what the exact value is, just makes sure you don’t get those 200+ line monsters.
  • "no-return-assign": 2, "no-param-reassign": 2, "array-callback-return": 2 - Can save you from those nasty ‘assignments that should be comparisons’ bugs inside functional array methods.

For rules that you don’t want or need to set the value from you can just set the error level: "rule": 2. Otherwise you need to pass an array with the value as the second item: "rule": [2, 'value']. What type of value it has differs per rule and is documented on the ESLint website.

We came up with a pretty decent configuration already! Check it out:

{
  "extends": "eslint:recommended",
  "parserOptions": {
    "ecmaVersion": 2016
  },
  "rules": {
    "array-callback-return": 2,
    "complexity": [2, 5],
    "eqeqeq": 2,
    "max-statements": [2, 7],
    "max-statements-per-line": [2, {
      "max": 1
    }],
    "max-nested-callbacks": [2, 2],
    "max-depth": [2, {
      "max": 2
    }],
    "max-lines": [2, 90],
    "no-eval": 2,
    "no-return-assign": 2,
    "no-param-reassign": 2,
    "no-var": 2,
    "prefer-const": 2
  }
}

Off the shelf configurations

I can imagine you don’t want to check out all the rules that ESLint has to offer for yourself, but just want a ‘good’ config. There are some pre-defined configs out there that you can use. I also created one called eslint-config-ngerritsen. You can use it as follows:

npm install eslint-config-ngerritsen -g
{
  "extends": "ngerritsen"
}

I got you covered selecting a pretty strict ruleset. It also has the rules for ES2016 and above. Ofcourse you are free to override any rule by defining your own rules afterwards or creating your own config using this one as a base. Other well know configs are eslint-config-google, eslint-config-airbnb and eslint-config-standard.

Conclusion

ESLint can be used to make your code style consistent, prevent errors but also improve code quality. One of the first things I do when creating a Javascript project is adding ESLint. There are so much rules and even plugins to choose from, the possibilities are endless. Just start with the recommended set or someone else’s config and fine tune rules as you go.

Note that just having an ESLint config in place will have no advantage of no one ever runs it. Make sure it runs automatically when you commit or push code using a pre-commit hook or a CI tool like Travis.

Reference

Related posts

Better Javascript apps by optimizing state

Client side applications hold a lot of state, some view related, some data related. But how do we make sure state is reliable and managable?

Immutable Javascript using ES6 and beyond

Writing immutable Javascript code is a good practice. There are some awesome libraries around like Immutable.js to help you with this. But could we survive with just vanilla, next generation Javascript?

Async ... await in Javascript

We’ve all been through callback hell, maybe we use Promises and Observables to get some relief. Will async await liberate us once and for all?