The Case for Auto-Formatters and Linters: Elevating Code Quality in Software Engineering

Jordan Frey · January 4, 2025

I will argue here that standardizing the formatting and styling of code on a team is not immaterial to building and deploying high quality software. Though I give examples using Python and GitHub Actions, the logic is applicable for any other language and CI/CD toolset.

Ugly Code Reduces Team Efficiency

Imagine a scenario where your colleague was just asked to enhance some code you built. As part of that work, new modules, classes, and functions must be created that will be reused elsewhere.

You have a different coding style than your coworker though, and while reviewing the diffs, you see a pull request marred with conflicting indentation patterns, varied casing styles, and in-line comments describing the functions inplace of docstrings (or worse, no documentation at all). Hopefully their code is not approved and merged by another engineer before you have a chance to complete your review.

This scenario is a reality on many engineering teams, and is not ~just~ an annoyance for developers that appreciate pretty code; rather, this reality can cause a multitude of issues including:

  1. visual clutter that distracts from the actual code logic being written
  2. non-standard style practices that cause IDEs to light up with lint warnings everywhere
  3. reduced efficacy of IDE auto-complete functionality when searching for the correct variable, class, function or method name
  4. negated functionality of IDEs ability to preview docstring descriptors for modules, classes, functions and methods
  5. increased time spent deciphering code that could have been spent building features, fixing bugs, or doing (quite literally) anything else.

Overall, such discrepancies create a hinderance a team’s productivity and will only become more problematic as projects grow. Ugly code (and undocumented code- thank goodness for PEP-257) almost certaintly means longer feature-to-production timetables; it can even lead to bugs from misinterpretted code. Team-leads at a minimum should encourage adaptation of some guideline to adhere to. Preferably, we should attach linters and autoformatters to our pull requests and CI/CD pipelines.

…Enter the Black Formatter

Autoformatters and linters are tools designed to automatically format or check code according to a set of predefined style guidelines or rules; they can also help make code that was written by ten developers and a GPT look like it was written by just one. One of the most popular options for Python is the Black Formatter, which helps format your Python code according to PEP-8 industry standards, the most famous of the PEP guidelines.

In the words of its documentation,

      “By using Black, you agree to cede control over minutiae of hand-formatting. In       return, Black gives you speed, determinism, and freedom from pycodestyle       nagging about formatting. You will save time and mental energy for more important       matters.

      Black makes code review faster by producing the smallest diffs possible.       Blackened code looks the same regardless of the project you’re reading.       Formatting becomes transparent after a while and you can focus on the content       instead.”

If you use VS Code, you can install the Black Formatter as an extension: https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter.

You can also install black formatter as a CLI utility: pip install black.

Other Useful Linters and Extensions:

You can use other linters as either an alternative or in addition to Black. Some recommended ones include:

  • flake8, flakeheaven, flake-8-docstrings, flake8-docstrings-complete
  • pylint
  • ruff

Each has their own advantages.

Linter Usage and Enforcement:

There are many strategies for how to best enforce linting and formatting on software engineering teams. I will suggest an approach that works best for me, and show how this can be done using CI/CD.

Requirements:

To follow this tutorial in its entirety, you must:

  1. Have VS Code installed
  2. Have Python installed, and a basic understanding of python syntax
  3. Install these VS Code Extensions:
    • Black formatter extension
    • Python extension
  4. Have a GitHub account and public repository to hold examples
  5. A basic understanding of git concepts, shell scripting, CI/CD, and preferably experience using GitHub Actions or Azure Pipelines

It will also be helpful- but not explicitly required- to use these tools locally as you work through this tutorial:

Workflow:

The easiest methods I have seen has been to first set up your IDE to autoformat your python code automatically upon saving.

After installing the Black Formatter VS Code Extension, I go into my VS Code user settings. On MacOS, these can usually be found at: /Users/<user>/Library/Application Support/Code/User/settings.json.

Add this to the json:

    "editor.formatOnSave": false,
    "[python]": {
        "editor.formatOnType": true,
        "editor.defaultFormatter": "ms-python.black-formatter",
        "editor.formatOnSave": true,
    },
    "black-formatter.showNotifications": "always"

With these settings, saving a python file will automatically be autoformatted according to Black style guides. If you keep formatOnSave set to false, you could alternatively right-click the editor, and select “format document” for the formatter to run.

On a team though, it can be hard to enforce contributor IDE settings. A CI/CD rule to check that code is styled according to team guidelines is a necessary second-check.

To properly test that this next piece works as expected, make sure to set the formatOnSave from the previous step to false. Then, put two Python files in your project directory (a src subdirectory is fine, as is the root- just keep it out of the .github folder). Keep one properly formatted, and the second improperly formatted. You can use these basic examples:

no_issues.py
# Properly formatted python code:
x = {"hello": "world"}
lint_this.py
# Improperly formatted python code, with added whitespace before the ending curly brace
x = {"hello": "world" }

Then, commit these changes to a branch that is based off of main, but is not main.

To check ONLY those Python files that have been modified against the main branch, first add this shell script to your .github folder under a “scripts” subdirectory:

modified_files.sh
# echos modified python files, but excludes those deleted
changed_files=$(git diff --diff-filter=d --name-only $(git merge-base HEAD remotes/origin/main) HEAD | grep .py)
echo ${changed_files}

Then, add these steps to your Actions workflow:

      - name: install black formatter
        run: pip install black

      - name: Save modified files to environmental variables
        id: get_modified_files
        run: echo MODIFIED_FILES=$(./.github/scripts/modified_files.sh) >> $GITHUB_ENV
      
      - name: return modified python files
        run: echo $(./.github/scripts/modified_files.sh)

      - name: Lint modified Python files with Black
        run: |
          if [ -z "$MODIFIED_FILES" ]; then
            echo "No Python files modified."
          else
            black --check --diff --color $MODIFIED_FILES
          fi

If there was any improperly formatted Python code, the deployment would fail. You can check this on GitHub Actions in your personal repository. Otherwise, to test this locally, first make sure you have the nektos/act CLI tool and run the act command in your terminal, with any options if desired. For example, I usually will run act <github_event_name> --container-architecture linux/amd64. For all options using this utility, please look into its documentation. For my own complete, working example on this workflow, you visit my Github repository: https://github.com/FreyGeospatial/github_actions.

Overall, this is a pretty simple example of what you can do to enforce a cleaner codebase on your team. I encourage you to explore other linters, including Flake8, or even ruff, the latter of which has gained popularity recently for being very computationally performant.

I hope you have found this helpful. If you have any comments, questions, or feedback- please reach out! And as always, happy coding!

Twitter, Facebook