Code reviews are great for exposing bad design and sharing high level knowledge, but bugs can sometimes go unnoticed. In this blog post we’ll show how to combine linting, formatting, and analysis tools to detect problems in the code that might not get picked up in a review.
What does good, maintainable code look like?
Coding languages and design patterns keep evolving continuously, so these tools and their configurations should be actively maintained as well for the best possible functionality, readability and up-to-date syntax. Developers shouldn’t be afraid to refactor the code either during updates and fixes even when code is already working properly, if they find out that there’s a better way of achieving the outcome. But, if these linting, formatting and analysis tools are used well from the beginning of the development as soon as possible, much less refactoring needs to be done in the maintenance mode that can eventually save a lot of time and valuable resources on all sides and ends.
We will cover three different types of helpful tools available for your code, what you can achieve with their usage and help you to get equipped with them in development instantly. But first, let's define good, maintainable code for the purposes of this blog post.
- Good code does what it is supposed to do
First and foremost, code should work. Everything else is pretty much worthless if the code is buggy. So the tools should help to eliminate potential bugs.
- Good code is easy to read and modify
If the code is hard to understand for humans, reading it will be painfully slow and modifications will probably lead to bugs. So the tools should increase simplicity and clarity.
- Good code follows common best practices
There's usually many ways to implement a piece of logic, some of them seemingly equally good. But we should be opinionated with our choices to encourage consistency not only within one codebase but across different projects. After all, familiarity increases readability. So the tools should increase consistency and warn about bad practices.
The tools for better code
-
Linters
- Linters such as ESLint and Pylint are used mainly for spotting logical problems with the code. They are especially important with interpreted and loosely typed languages like JavaScript and Python, since they don’t have the extra compilation step to catch occurring errors. JavaScript especially is a very dynamic and quirky language that can lead to weird unintended behaviours if best practices are not followed. For example, one should prefer
===
and !==
instead of ==
and !=
because JavaScript's type coercion is very unintuitive to say the least. Luckily there's an ESLint rule for that and other similar common practices.
- ESLint is also very extendable, so it can be used and configured to warn about almost everything. To extend the default ruleset, you should probably use plugins for all the specific frameworks, maybe even some libraries and for accessibility (e.g. eslint-plugin-jsx-a11y). Since every project is different, the rules should be configured to the project’s version control. However, the closer the configurations are to recommended defaults, the easier it is to maintain and follow best practices. A great starting point is to use the latest version of all the packages and by using the recommended rulesets of each one (see the example configuration). And if a rule doesn’t fit within the project, it can easily be disabled by setting it “off”.
- Linters are most helpful when integrated into the editor to scan the code as you type for the fastest possible feedback. Possible errors are then always highlighted and they can be fixed quickly. For example, let's say I'm working on a React app and implementing a new form. Without ESLint and eslint-plugin-jsx-a11y it's likely that the form ends up being hard to navigate with keyboard or impossible to fully understand with screen readers without anyone even noticing it until it has caused real damage. But with a well configured linter and editor I get instant instant warnings if an interactive element doesn't have keyboard events or images don't have nonredundant alt attributes.
- Linters can be used as style guides for formatting as well, as in popular eslint-config-airbnb, but in my opinion it’s better to force good style with automatic formatters and let the linter focus on logical issues only.
// .eslintrc
{
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module"
},
// extend default rulesets
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:import/warnings",
"plugin:security/recommended",
"plugin:jsx-a11y/recommended",
"plugin:prettier/recommended"
],
"env": {
"es2020": true,
"browser": true,
"node": true
},
// exclude unneeded rules
"rules": {
"react/prop-types": "off",
}
}
- Formatters
- Formatters such as Prettier and Black are used to force a consistent style for the project by printing it out according to their set of rules. Formatters are used in addition to the linters: Linters are used for code quality, formatters for formatting rules. Besides JavaScript and TypeScript, Prettier can also format CSS, Less, SCSS, HTML, JSON, Markdown and YAML to name a few. Because formatting of the code doesn’t really affect how the code works, it’s not so important what the set of rules are, but that there are common rules of some kind being used. That’s why the best formatters are opinionated and need no configuration at all. While most formatting rules are up for debate, the defaults of them are optimized for readability and there’s rarely a logical reason to differ from that. For the best efficiency, it’s probably best to have a formatting step in pre-commit hook so that it’s impossible to commit badly formatted code, thus the developer can write the code in any format that feels the most comfortable. In addition, formatters can be integrated with the editor to format code on file saving. The benefits are enormous: Huge time savings, when it’s no longer needed to make decisions about style and no longer required to manually type whitespaces or other optional characters. Formatting huge codebases can be done in a matter of seconds. Using formatters also makes it super easy to start contributing to a codebase when it’s no longer needed to learn the formatting conventions of the project.
// package.json
{
...
"prettier": {},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,md,html,css,json,yml}": "prettier --write"
}
}
- Static code analysis
- Static code analysis tools, such as SonarQube, perform analysis and provide metrics on your code base. SonarQube has a great support of 27 different languages even including CSS and HTML. It is great at detecting hard to catch bugs that other tools or humans may miss by exploring all the possible execution paths. It warns about code smells that make the code harder to maintain even if code is working as intended, in example duplicate code, too complex code and uncovered code. It also warns about vulnerabilities and potentially insecure code plus measures reliability, security, maintainability, coverage and duplications of the whole codebase and new upcoming code. SonarQube is best used as a part of the CI/CD pipeline to scan the code every time new changes are pushed to a monitored branch. As a bonus there’s also SonarLint extension for editors that can be used in addition to SonarQube to warn about errors during the code writing process.
Conclusions
All of these tools mentioned are quick and easy to adopt. They support and follow best practices, plus they provide a lot of bang for the buck by reducing the time and costs to write good code. The most effective combination and usage of them is to: Lint as you type, Format in pre-commit hook and run SonarQube in CI/CD pipeline. They work as the automatic code reviews for changes which is often enough in software maintenance tasks. All of them can be adopted at any time in the software’s lifecycle as well but it’s better, more effective and easier to start using them right from the beginning. When they are used along with great tech choices, design and regular updates, it’s secured that the codebase stays as effortlessly maintainable as possible now and in the future.