Maintenance Matters: Default Formatting
Never think about formatting again with these simple tips. In the latest entry of the Viget Maintenance Matters series, readers will learn about Linters and Auto-formatting.
Engineers tend to have a strong desire to automate all everything. Whether it’s worthwhile to do so or not depends on how frequent and time-consuming a task is. To the surprise of no one, there’s an XKCD for that.
How code is formatted is one of the things that absolutely should be automated. Manually indenting code? Writing semi-colons? Arguing about which color to paint the bike shed? But who has time for that!?
The software development lifecycle is continuous. Maintainability measures how sustainable changes over time are for a given project. Auto-formatting is a small but essential piece of that story that helps reduce bike-shedding, maintains consistency and quality of the code, and allows collaborators to contribute more easily. For additional considerations, look at Viget's ongoing series on why Maintenance Matters.
Which tools should I use? #
EditorConfig “helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs”.
It’s especially useful for ensuring consistent application of newlines at the end of files, that
Makefilesuse tabs and not spaces, or the use of 4-space indentation for
Another helpful feature is allowing individual authors to override these settings for their local environment while keeping their contributions consistent with the project’s style guidelines.
black, which can be integrated with the linter
prettier, integrated with
standard, which is an opinionated linter that wraps
rubocopand includes auto-formatting
sh, which includes
shfmtfor formatting shell scripts,
shellcheckfor linting and avoiding common pitfalls
mix format, included by default
Note: In most cases, you’ll probably want to avoid running these tools on vendored or third-party dependencies. Some tools handle this well out of the box, but for others, you may need to configure them to ignore specific files or folders.
One handy approach for
eslint is to pass
--ignore-path .gitignore (with the logic being that anything that isn’t version controlled likely doesn’t need to be linted, either). However, this won’t suit every project’s needs.
Which rules should I use? #
I can’t tell you that. You and your team must agree on which approaches make sense for the codebase being maintained. Still, it’s a good idea to stay somewhere in the realm of standard, widely adopted approaches. Don’t settle on 3-space indentation, please.
To fix or not to fix? #
In local development, it can be useful to run auto-formatting tools and have them immediately fix issues in your source files so you can commit the changes. If your editor supports auto-formatting on save, you probably won’t need to run the checks to generate fixes manually.
However, in CI environments, we often want to validate that formatting is correct and ensure the program exits with a non-zero exit code to fail the build (most of them do by default).
All of these tools support various flags to control their behavior. To enable automatically fixing source files
--write. For usage in CI
mix format uses
Validating that consistent formatting has been applied before contributions are merged is a mandatory step to ensure high code-quality. You should run auto-formatting tools in your Continuous Integration environment and fail the build if the formatting is incorrect. Proper use of editor integrations and or git hooks should usually catch this before code hits CI, but it’s best not to rely on those measures.
Ensuring violations are caught before code hits CI checks can save money and shorten feedback cycles by giving developers immediate feedback without waiting for CI workflows to run. Git hooks are a handy way to configure when to run these checks, but since they (
.git/hooks/*) are not committed to source control, it’s not quite as simple as adding the necessary scripts to the
.git folder. Tools like
husky can help manage these tasks and ensure all contributors end up with the same local verification steps.
Icing on the cake #
IDE and editor integrations are where auto-formatting really shines. You can completely forget the rules and how they get executed if your editor supports auto-formatting on save. Plus, it’s nifty to see the code magically fix itself in front of your eyes when you slam
⌘ + S or (
A note on performance #
As the size of a project grows, operations like linting and auto-formatting can become more expensive. At a certain point, you may need to take extra measures to reduce unnecessary work.
Caching checks between runs
Filtering files to be checked
If only a handful of files have been changed since a known, good starting point (e.g., a previous commit), you can save work by only running the formatter on the files that have changed. Most of these tools optionally support taking a list of files on
stdin or being passed a list of files as a flag.
You might also choose to use something like
lint-staged to facilitate the process of determining which files to run these programs on.
lint-staged is designed to be used before committing files (e.g., in a pre-commit hook) and not interactively on save.
In the quest to create more maintainable applications, auto-formatting is a critical tool that helps maintain consistent, quality contributions from a wide variety of contributors. It eliminates unproductive discussions surrounding what often ends up being trivially important decisions. Setting up linting, auto-formatting on save, git hooks, and CI checks should be one of the first things you do on every new project.