Linters aren’t in your way. They’re on your side
Linters are continually evolving. At the core, Wikipedia defines them as tools to analyze “source code to flag programming errors, bugs, stylistic errors, and suspicious constructs.” Linting can be seen as a static analysis that happens outside of a compiler; that is, they process and examine source code without compiling it into a binary.
What linters can do for you
Lint tools can be used to:
- Enforce a style, like pylint for Python or PMD for Java
- Prevent indexing beyond the end of an array
- Catch mismatches in variable types
- Spot potentially dangerous data type combinations
- Detect unused variables and unreachable code
- Identify dereferencing null pointers
- Flag non-portable constructs
- Monitor for code complexity
- Detect potential bugs
- Warn about security issues
- Finds possible memory leaks
- Even point towards signs of code smells
Linters vs formatters
Note that it is a somewhat blurred line between formatters and linters, especially since formatters are getting more opinionated for a lot of languages recently. Go ships a built-in one (gofmt), Rust develops an official formatter (rustfmt, invoked via `cargo fmt`), Python’s Black is incredibly popular and so is Prettier for JavaScript.
While linters can check formatting and adherence to code style and warn you if you’re off, formatters go straight ahead and just apply a preferred formatting and code style completely automatically. Most of the time, the decision to create a more coherent code style on a team will include the introduction of both linters and formatters.
Not for compiled languages?
Yes and no. There seems to be a tendency to reach for lint tools more when you want to check for issues that would otherwise slip through and break things later on dynamic languages, think Ruby, JavaScript, or Python. If you are working in an ecosystem where you can rely on the compiler to catch type issues and other problems, the need is less immediate. There can be benefits for the individual or team nevertheless. If you make use of all above mentioned checks to your advantage, you can:
- Fix many problems before committing code and wasting time compiling
- Eliminate bugs before they make it to prod
- Accelerate the time to market of new features
- And most of all make developers’ lives easier, with a smoother process
So why isn’t everyone a fan of linters? It’s fair to assume that it is less about developers turning their blind eye to the benefits of linting tools, but rather there are specific circumstances where the benefits are overshadowed by some down side.
What are the cons?
- The most obvious argument against it is about it breaking concentration. If you are trying to get into the flow and crank out lines of code, a bunch of flags and warnings popping up can take you out of it.
- Another danger is developing warning fatigue. This is often the result of false negatives and making assumptions about the reliability of linters. If you get into the habit of ignoring all the flags, your linter can protect you as little as the warm, fuzzy-feeling you get inside from gazing at the ever-present check engine light.
- Using a linter on a massive existing code base is something many would advise against as “implementing a linter on a mature project becomes a tremendous task.”
- In the same way, early prototyping might not be the right time for clean-up.
In some situations, it might just not be worth the hassle. If you are relying on a compiler to catch potential problems, that is a stronger assurance that code will run correctly. But if you are working in an interpreted language and cannot rely on that ultimate last check, linting tools can be a way to close the gap for your correctness needs.
The myth that “real programmers don’t need linters, they block creativity” seems prevalent, but harmful. With linters evolving, there are good reasons to think about them in a new way. Here is how to be less annoyed with them.
How to change your (team’s) mind about linters:
If you feel you suffer from linter frustration or have trouble convincing members of your team of the benefits of making use of them, here are some ways to think about it more than just as annoying error messages:
How to embrace linters as a team
Let the linter build the bikesheds. Bikeshedding, is when people fall into the trap of discussing something small and trivial around a more complicated project. Tabs? Spaces? Double quotes? Single quotes? Let the linter hoover up all these little style questions before they take up time during pull request (PR) reviews with peers. Get rid of the style questions that are truly not worth arguing about.
The introduction of linters might not only free up time during code review for more important decision making and learning, it might even reduce the number of requests in total. Lint out those that were about style bickery. Standardizing your code is a great way to move the conversation to a more productive level.
Welch Canavan puts it nicely on his blog: “You will never be on a team that is completely aligned on best practices. Part of being a great team member is setting your ego aside and recognizing that it is much more important to agree on a standard than your standard.”
Let the linter create a unified voice. The reality is that code has many (sometimes many, many, many) authors. But the fact that many people have contributed should not be visible in the code. In his talk, Matt Rose describes this as the narrative voice of the writing. You can have oral or written agreements on style conventions to keep this voice consistent but they will never work as well as automatic convention. Your team of many code authors can write with one indistinguishable voice – just like Shakespeare.
Let the linter help keep your code healthy. Code smells, style guide mismatches, security issues or poorly designed code—many modern linters will help you look after your code base’s health. You can see linters as a check-up, a way to measure your code’s health to then discuss in retrospective or architecture meetings.
Let the linter help you automate. Make your life easier and invest the time to automate what can be automated. Most linters are command-line tools first and foremost and thus are excellently suited for automation. Examples are Prettier, Pylint, ESLint or RuboCop.
How to embrace linters as a team as individuals (on a team)
Embrace the learning opportunities. It might be tough to change your mindset from seeing each pop-up not as on obstruction, but, as Matt Eland writes, “Sometimes, just searching and understanding a warning will teach you new things about programming and shore up vulnerabilities you didn’t even know you had.”
Gamify it. If it already feels to you like “you vs. the linter” why not take that to the max. Block out 15-minute of time. And then, much like getting an inbox to zero, see how many warnings you can tackle during this period.
Code drunk, linter sober. Linked to the above, it is all about limiting linters (and potential frustrations with it) to a specific time. If you are in the flow, cranking out that prototype, turn it off. But allow time to address some of the flags before you waste that time with a peer in a review. And of course, we are by no means encouraging having a drink at work. Stay hydrated tho!
Think of linters as a pedantic friend. We enjoyed the suggestion of treating a linter as a weirdly pedantic code review friend. Assume that they know more than you. But since they are your friend, you can also tell them to go away when you’re trying to focus. Just make sure to ask them to come back later to check your code and make you look smart in front of your peers. Note: they are a loyal friend, but take their pedantry with a grain of salt and add your human judgment.
Remember: Linting isn’t just for you. It is for the other people on the team.
Linting can help with the heterogeneous knowledge on a team. For juniors, a linter is like an invisible senior team member that points out style guideline documentation, causes them to pause and question steps, and ultimately helps learn a new language. For seniors, it is an easy way to enforce, not their style, but the use of a consistent style on a team. Leading by example becomes more visible and somewhat built in. It saves many shoulder taps and time during code reviews.
The process of linting can be time consuming when starting out a project; it requires the discipline to catch linter messages from your first line of code. It is, however, worth taking advantage of the ways to customize. Make use of best practices. Some companies publish their configuration settings. Like Airbnb’s .eslintrc. Github not only published their linter config, but even open-sourced their unified version of over 20 linters last month.
Our parting thoughts are summed up nicely in this contribution “Something I think that programmers don’t always appreciate is that linting isn’t an either/or proposition necessarily. A linter is exactly like the spelling/grammar checker in your word processor. It’s there to advise you.”
Tags: code-analysis, debugging, linters
7 Comments
I have a love/hate relationship with linters. I really like how they keep style more consistent, code cleaner, help prevent security issues, often help with readability, and a lot of other features, but having a ton of flags or squiggles in the IDE can definitely be annoying. Sometimes I just want 5 min before they show up so I can fix the problems I know exist before being nagged about them. And then I sometimes want them to so up immediately so I can make sure I haven’t done something stupid but not obvious.
I’ve joined an old project that had linters added to it just before I arrived, with me taking over that project as it’s sole dev. This project happened to have over 14k build warnings when I came onboard. I worked on them as I did other things, but I eventually talked my manager that we needed to clean them up, so we dedicated most of my time to that. After 3 months and a bunch of bleary eyes (including the code reviewers), we had less than 20 warnings left. The code was in a much better state and seriously more reliable. A couple of months later and a lot of research later, I had the last few warnings figured out and eliminated.
I think it made me a better programmer to go through all of that, even if it was a bit of torture. I think it helped me learn what not to do as well as how to simply avoid some of the problems the linter pointed out. One of the first things I do in a project now is to add linters.
Sure, sometimes they tell me to do some stupid stuff or have rules that contradict themselves, and then there’s the “never ending” warnings after suggested changes, but in the end, we can still disable some rules for the whole project or a small section. And we can do it permanently or temporarily, so we don’t lose our minds or we can concentrate on other, more important things.
Linters really are a programmer’s best friend, as well as their worst nightmare, sometimes. 🙂
> impossible to introduce to old, large code bases
Why? We can configure them and we don’t have to make deployment dependent on there being zero linter warnings.
I use SwiftLint for my work (I actually have a write-up on it, here: https://littlegreenviper.com/miscellany/swiftwater/swiftlint/).
I believe in “turning error checking up to 11.” I have an xcconfig file that does the Swift equivalent of “-wall”.
The nice thing about these tools, is that it makes quality programming a habit, not just an act. Once I developed a habit of writing good code, I almost never get complaints from the build/compiler or SwiftLint. I don’t even need to give the matter any thought.
I love linters. Help me keep my code cleand and let me aware of some stuff that otherwise I wont be able to even evaluate if to keep or change.
For C++ we’ve used a script wrapper on top of clang-tidy. Besides the expressive diagnostics, clang-tidy can make suggestions as to how to fix the warning, and can implement that suggestion automatically (opt-in). And c-t can be customized with additional warnings that are useful in your code base, which we’ve done. We use the wrapper to improve the signal-to-noise ratio, allowing through only warnings we *really* want the developers to see and act on. The tool is highly regarded in-house.
Thought hits me as I read this that possibly if you need this kind of thing you should not be developing code in the first place…
I LOATHE lint.
Coding in a new python project using gitlab CI/CD environment. Having spent a good part of my career working on Cobol programs it just boggles my mind that the creators of lint apparently want to take modern, elegant programming languages and push them back to the Cobolesque era of obsessing about minutiae like line lengths, numbers of lines in a module, the (gasp!) presence or absence of a “trailing newline” at the end of a file, or other completely, utterly idiotic trivialities.
Such as disallowing single character variable names, except apparently in a tribute to of all things, fortran, “i”, “j”, and “k”. Sigh. I wanted to use “x” in a mathematical formula, but apparently that’s a violation that simply cannot be allowed. Ugh. Yes, I am well aware that I can modify the “.pylintrc” file to allow whatever I want, but I’d have to persuade the rest of the team and since they’ve drunk the lint Koolaid, it’s just not worth the effort.
Just this past night, while working on an urgent deadline, I had to deal with several of my builds being blow up by lint because of these GRAVE errors:
“C0302: Too many lines in module (1002/1000) (too-many-lines)”
“C0304: Final newline missing (missing-final-newline)”
Seriously. A build blown up due to a missing trailing newline. Way to focus on what’s important! Unbelievable.
And let’s not forget the unforgivable: Line length too long.
Lint distracts programmers by forcing them to think about and respond to trivial and petty nonsense. Instead of focusing on writing elegant code, my colleagues constantly break logical coding constructs into tiny, multi-line pieces so as to not inadvertently violate the “line too long” rule and have lint crash their builds. But doing that of course, leads to modules that quickly exceed the arbitrary “too many lines” rule.
And WHY? Seriously, WHO CARES how long a line is, as long as the code is readable? WHO CARES if a module has more than some random, arbitrary number of lines in it, as long as it’s reasonable for the project being developed. WHO CARES if I use the single letter variable, “x” in a mathematical equation? WHY IS THAT BAD? Answer: It’s not.
The stupid lint line-limit check doesn’t even exclude comment lines from it’s count. “No-op” lines that will never be loaded into memory, because they do nothing. Yet they are included in the total module line-count rule. WHY? What is the point of this, really?
Programmers should be entrusted with writing the best, most elegant code they can that – to the extent possible – conforms to standards instituted by the organization they work for. They should not be subjected to the utterly arbitrary, practically random, completely pointless “Nannyisms” imposed by lint. If you love this type of stuff, go back to coding in Cobol. Or better yet, RPG.