Industrialize Your Coding Style With Pre-Commit Hooks

Industrialize Your Coding Style With Pre-Commit Hooks

See how to enhance code quality and gain time

Guillaume Vincent
Guillaume Vincent

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 initcommand. 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: 123
error: 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: 1
main.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 hook
reformatted 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.

Resources

Google Style Guides
Style guides for Google-originated open-source projects
Git - Git Hooks
pre-commit
Programming

Guillaume Vincent

DevOps Engineer & AWS Certified Solution Architect. Cloud enthusiast and automation addict