Always lint

“lint (n): undesirable bits of fiber and fluff found in sheep’s wool”

Wikipedia

Linting is one of the first things I like to set up on a new project. Especially when working with interpreted languages, it’s reassuring to develop with a mechnical helper pointing out mistakes, problems and potential improvements.

Linters exist for pretty much every language out there, and are a great addition to the development process. Here’s why.

Linters catch bugs

Although most people don’t think of it as their primary purpose, linters can catch bugs before they even make it to execution, let alone production.

“10. All code must be compiled, from the first day of development, with all compiler warnings enabled at the compiler’s most pedantic setting. All code must compile with these settings without any warnings. All code must be checked daily with at least one, but preferably more than one, state-of-the- art static source code analyzer and should pass the analyses with zero warnings.”

NASA’s ten coding commandments

As a language community starts to recognise recurring causes of bugs, they can be incorporated into linting tools. Common examples might be assignment in conditions, non-terminating loops, operator mismatch, implicit type coercion, null pointers, division by zero and so on. It varies a lot by language. These are simple sources of bugs, but they can also be subtle, and that’s why the linter is often better at identifying them.

Regardless of how well a human being is able to spot these errors, the point is that a machine can also do the job. Hopefully human time is at a premium, which is why it’s better to have the linter catch these wherever possible. What can be automated should be automated.

Linters improve quality

After identifying some kinds of straight-up bugs, a linting tool can also improve code quality in terms of readability, performance and design.

An area of code can function correctly and pass all its tests, but still have basic performance problems or be a mess that no-one enjoys working with. Static analysis tools can help with both of these issues.

“What we need at such times is an infinitely patient, anal-retentive expert to inspect every inch of our code. This expert must be more thorough than any compiler, and should report back in a completely dispassionate way everything that is potentially wrong with our code.”

How to use lint

In terms of performance, the linter may be able to spot a non-optimal order of expressions, expensive operations in a loop and other long hanging fruit. Tweaks in regexes, mergeable or redundant code and more performant solutions to common problems may also be suggested. The linter will provide this analysis faster and more consistently than a human reviewer, freeing them up to focus on deeper insights into the code.

Basic readability and maintainability issues such as unused variables, functions and parameters are also common prey for the linter. These are the sorts of cruft that can easily accumulate over time in an application, because they’re less obvious to the human eye.

As well as performance and readability, static analysis can highlight potentially poor design choices such as overly complex class hiearchies, excessive function parameters and high cyclomatic complexity. Anyone who has inherited a legacy code base has had that sinking feeling on opening a file to find code with a dozen parent classes, half the application passed around in function parameters and a soup of ifs, elses, fors, trys, catches and returns sprayed across the editor. A linter can prevent that situation from emerging.

You are probably conscious of these issues anyway while coding, but it’s still beneficial to have the linter, because:

The last point is one of the things I like most about static analysis tools: they reduce cognitive load whilst coding.

Learn about the language

Beyond the more obvious benefits of reducing defects and improving quality, I also like static analysers because they help me to learn more about the language I’m using.

When the linter reports something that I hadn’t spotted, it’s a great learning opportunity. If I was previously unware of the issue, I’ll certainly Google it, read what others have said about it and incorporate that into my work. I’m very likely to remember this due to the slight chastisement of the linter, the personal research and the specific example I can attach this knowledge to (my own code).

Even if I don’t agree with what the linter has reported, it’s still worthwhile knowing the rationale behind it and what other developers think about the problem.

Kill the paperclip

The point about disagreeing with the linter is an important one, as it’s one of the most common objections and obstacles to getting these tools involved in a project.

The infamous paperclip from Microsoft Office springs to mind here. People hate it when a mere machine treats them as an equal. This applies even more so to developers, who tend to have quite a high opinion of their own level of knowledge.

Linters are often felt to be overzealous, pedantic and insensitive. This is hard to refute as that’s kind of the point. The linter is there to nitpick, whinge and suggest where a human being might not.

However, there are a couple of solutions to this.

Firstly, any static analysis tool worth using will be configurable down to the rule level. If a rule is genuinely obstructing progress, it can either be disabled for a particular offending line, or for the whole project. Many rules can be loosened to allow for existing areas of the code that should be better, whilst not letting the situation get any worse. Discussing and settling on a rule set is a constructive thing for a team to do in any case.

Secondly, it’s good to recognise that standards are beneficial in themselves. That is, having a standard is generally positive regardless of what the standard actually says. Some reasons for this include:

Go has done a great job of this by not only having a clear standard, but an automated tool (gofmt) to enforce it. This lets everyone focus on more important problems.

Linting vs testing

There are some clear parallels between linting and testing. Both reduce bugs, improve quality and save time in the long run.

One nice thing about linting is how easy it is to set up and use. You don’t need to write the checks yourself, and can immediately apply some accumulated wisdom to any codebase. Static analysis tools are almost like a set of tests you get for free out of the box.

Unit, functional and integration tests can take a lot more set up before you can even start to write the assertions. Stubbing, mocking, configuring and so on can easily become time-sinks. Linters, on the other hand, tend to work pretty quickly and independently. They also run faster than a lot of automated tests.

Having both linters and an array of automated tests at different levels is important. The two complement each other, and the more automation you have, the better.

See also