How To Automate Your Coding Style With Pre-Commit
See how to enhance code quality and gain time
Code is not something frozen through time but it evolves with the team and the project. Writing good code does not imply only writing code well suited to the use case and optimized but also readable. Sometimes during the project lifecycle, the code readability may decrease. This could come due to a variety of factors as few described below to give you a picture :
- You have not defined a clear coding style and each developer has his own
- There is a newcomer and he has his own coding habits from his previous experience
The Coding Style
Quality comes from standards
Standardization has a lot of advantages for any business. The software industry does not differ. The standards are required for persistent successes. Organizations need to deliver functioning software and respecting time constraints to ensure their growth. Developers can underestimate the quality criteria when they are required to complete their duties in a short span of time. Gradually each code change is more and more time-consuming and complex.
The adoption benefits
A coding standard assures that all the project’s developers are attending the same guidelines. Each developer should be the guarantor while coding. The code has better readability and good consistency.
Consistency means that the final application code should look as it has been written by the same developer and in one go.
Readability is important because it is tiring to switch between few lines of code with a different style. Your eyes need to accustom every time. This could promote by negligence bugs born, security failures, and potential performance drops.
A coding style brings several great assets :
- Boost team efficiency
- Reduce the risk of project failures
- Minimize complexity
- Facilitate bug fixes
- Enhance cost-efficiency
- Improve maintainability and code reviews
One process couldn’t be enough
Writing a document to promote a coding-style is a good first step. Nevertheless, it does not save people time when they have to refer to it and track exceptions in the code. For sure a lot of exceptions could fall through the crack. For long-term maintainability and to avoid careless failures. In the next section, we will look at how to solve it using Git features.
Git Hooks to the Rescue
What is a Git hook?
Git can fire off custom scripts depending on which actions occur. The Git hooks are stored in the .git/hooks
directory located at the project root. It is created when you initialize your project with thegit init
command. The location already contains some scripts at its initialization. Having a look can give you the inspiration to create some new git hooks in any scripting language you desire:
$ ls -l .git/hooks
total 112
-rwxr-xr-x 1 gvincent staff 478 Jan 28 16:47 applypatch-msg.sample
-rwxr-xr-x 1 gvincent staff 896 Jan 28 16:47 commit-msg.sample
-rwxr-xr-x 1 gvincent staff 4655 Jan 28 16:47 fsmonitor-watchman.sample
-rwxr-xr-x 1 gvincent staff 189 Jan 28 16:47 post-update.sample
-rwxr-xr-x 1 gvincent staff 424 Jan 28 16:47 pre-applypatch.sample
-rwxr-xr-x 1 gvincent staff 1643 Jan 28 16:47 pre-commit.sample
-rwxr-xr-x 1 gvincent staff 416 Jan 28 16:47 pre-merge-commit.sample
-rwxr-xr-x 1 gvincent staff 1374 Jan 28 16:47 pre-push.sample
-rwxr-xr-x 1 gvincent staff 4898 Jan 28 16:47 pre-rebase.sample
-rwxr-xr-x 1 gvincent staff 544 Jan 28 16:47 pre-receive.sample
-rwxr-xr-x 1 gvincent staff 1492 Jan 28 16:47 prepare-commit-msg.sample
-rwxr-xr-x 1 gvincent staff 3650 Jan 28 16:47 update.sample
The different types of Git hooks
Globally, the hooks can be sorted into two categories :
- Client-side hooks are triggered when committing or merging. It concerns only the local actions on your repository
- Server-side hooks are triggered on network operations such as receiving pushed commits. The perfect example is the case of a CI/CD job that executes actions on a Git branch when a new commit is detected.
Next, we will use client-side hooks before committing to ensure we respect the coding style and help to debug.
The Pre-Commit Framework
Pre-commit is a framework written in Python. It gives the opportunity to reuse existing pre-commit hooks developed by the community. To manage them it uses a YAML configuration file containing dependencies and versions. The list of supported hooks by the project is wide you should find your happiness
Installation
$ pip install pre-commit
$ pre-commit --help
usage: pre-commit [-h] [-V]
{autoupdate,clean,hook-impl,gc,init-templatedir,install,install-hooks,migrate-config,run,sample-config,try-repo,uninstall,help} ...positional arguments:
{autoupdate,clean,hook-impl,gc,init-templatedir,install,install-hooks,migrate-config,run,sample-config,try-repo,uninstall,help}
autoupdate Auto-update pre-commit config to the latest repos' versions.
clean Clean out pre-commit files.
gc Clean unused cached repos.
init-templatedir Install hook script in a directory intended for use with `git config init.templateDir`.
install Install the pre-commit script.
install-hooks Install hook environments for all environments in the config file. You may find `pre-commit install --install-hooks` more useful.
migrate-config Migrate list configuration to new map configuration.
run Run hooks.
sample-config Produce a sample .pre-commit-config.yaml file
try-repo Try the hooks in a repository, useful for developing new hooks.
uninstall Uninstall the pre-commit script.
help Show help for a specific command.optional arguments:
-h, --help show this help message and exit
-V, --version show program's version number and exit
Automate the coding-style
We are going to use pre-commit to ensure our python script is respecting PEP8 guidelines. Python is a good candidate for this example because it requires good discipline. We will delegate this to pre-commit and focus more on the code features using the following pre-commit hooks :
- Black is a hook that can reformat the code in place.
- Flake8 is a hook checking that the code format is compliant with PEP8.
- Trim-Trailing is a hook detecting extra whitespaces.
Here the YAML configuration file :
repos:
- repo: https://github.com/ambv/black
rev: stable
hooks:
- id: black
language_version: python3.9
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: trailing-whitespace
- id: flake8
Then we install all the hooks with the pre-commit command :
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
As an example, I use a python script containing some errors done on purpose :
We add the faulty script and commit it to git :
$ git add main.py
$ git commit -m "Added initial commit"
black....................................................................Failed
- hook id: black
- exit code: 123error: cannot format main.py: Cannot parse: 20:0: return reduce(lambda x, y: x - y, args)
Oh no! 💥 💔 💥
1 file failed to reformat.Trim Trailing Whitespace.................................................Passed
Flake8...................................................................Failed
- hook id: flake8
- exit code: 1main.py:1:1: E902 TokenError: EOF in multi-line statement
main.py:20:1: E305 expected 2 blank lines after class or function definition, found 0
main.py:20:1: E112 expected an indented block
In the result above, we can see that pre-commit fails and blocks the commit because the black hook has failed. We correct the python errors but not the extra whitespaces :
We add the changes to git and retry a commit again :
$ git add main.py
$ git commit -m "Added initial commit"
black....................................................................Failed
- hook id: black
- files were modified by this hookreformatted main.py
All done! ✨ 🍰 ✨
1 file reformatted.Trim Trailing Whitespace.................................................Passed
Flake8...................................................................Passed
Black hook did the reformat of the PEP8 warnings and the whitespaces. The commit fails because we need to add Black modifications to Git :
$ git add main.py
$ git commit -m "Added initial commit"
black....................................................................Passed
Trim Trailing Whitespace.................................................Passed
Flake8...................................................................Passed
[master (root-commit) eb8b592] Added initial commit
1 file changed, 27 insertions(+)
create mode 100755 main.py
We can also launch directly the pre-commit command on the files to check everything is ok :
$ pre-commit run --all-files
black....................................................................Passed
Trim Trailing Whitespace.................................................Passed
Flake8...................................................................Passed
Conclusion
To sum up, we have seen that adopting a coding style in a project contributes to boosting productivity and guaranteeing quality.
As developers, we have not restricted ourselves to a written process to solve the issue. We have explored the possibilities given by the git hooks to provide a repeatable and reliable solution.
Finally, we have gone further to facilitate the coding style distribution across the team and the projects using the pre-commit framework. In another article, we’ll see how to use pre-commit with Terraform in a CI project to go deeper in automation.