Enhancing Code Quality With Github Actions

Arvind Singh Rawat
The Startup
Published in
10 min readOct 17, 2020
Photo by Luca Bravo on Unsplash

How to write production-ready code?

The first step for writing a production-ready code is that we write a well-documented code which aligns with the programming language ethics. But how should we implement this and make sure our team follows the same practices?

This article will shed light on one of the features that Github offers, which enables us to achieve the above step and much-more.

Let us delve deeper into this feature and explore a way to maintain the production-level code quality.

Github Actions

Github actions allow users to automate tasks of the Software development lifecycle like Code Review, Building, and Deployment.

Github Actions provides the feature to attach some workflows with a repository. These workflows will get automatically triggered by events like Pull Request, Push, etc. and perform some actions based on its definition.

Workflow: A workflow is the collection of one or more jobs that will be automatically triggered by any event or can be scheduled by the user. It is used to build, test, deploy the code of your repository.

Workflow is defined in the YAML files.

Creating Workflows

For this article, I have picked the Python programming language for demonstration, for other languages, some actions and steps may differ, but the basics will remain the same.

Let us try creating a workflow which checks the Python version that we will be using in our Runner to perform workflow jobs.

Create a .github/workflows directory in the root of your repository. This directory will store the definition for our workflows in YAML files.

  1. Inside the workflows directory, create a YAML file where we will write a definition for our workflow. Let the file name befirst-workflow.yml
  2. Add the following lines in the above file:
name: first-workflow
on: [push]
jobs:
check-python-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.6.9
uses: actions/setup-python@v2
with:
python-version: 3.6.9
- name: Check Version
run: python --version

3. Explanation

  • name: first-worflow is the name of the Workflow in the Github actions.
  • on: [push] specifies that this particular workflow will run whenever push event occurs in this repository.
  • jobs: specifies the list of job(s) in this workflow.
  • check-python-version: is the name of the job. This job will print the python version of our environment in the runner.
  • runs-on: ubuntu-latest specifies the operating system for the runner on which this job will run. Users can also specify other Operating systems or self-host this process.
  • steps: specifies the series of actions to complete this job.
  • - uses: specifies the predefined actions as a part of this job.
  • - uses: actions/checkout@v2 specifies the action which helps to download the specific commits(more specifically, current commit) in the runner as part of this job. Although the repository code is not used in this job, it will be used later.
  • - name: Using these, users can specify the human-friendly name for a particular step instead of the complex command or action.
  • - name: Set up Python 3.6.9 Name of the current step
  • uses: actions/setup-python@v2 Sets up a Python environment in the Runner.
  • - with: specifies the map for the input parameters of the action.
  • - python-version: 3.6.9 is a Key-value pair of the parameter used by setup-python action. It specifies the python version used in the job.
  • - name: Check Version Name of the current step
  • run: python --version Checks the python version. (Ultimate objective of this job).

4. Commit and push the code in the Github repository.

5. As soon as the code is pushed successfully, go to the Github actions tab in the Github repository.

6. Here we will find the list of events (in our case, the PUSH event) for which workflow is executed.

7. We can check the status of the workflow by using the icon next to the Event name.

8. For more details, we can click on the event name and we will be redirected to the page with more detailed information. On the left pane, we will get the list of Jobs for the selected workflow. By clicking on any one of the listed Jobs, the right pane will get updated by the step-by-step information of that job.

For more information, visit documentation on Github Actions.

Now the question arises, How will the Github Actions help us to maintain the Code Quality?

For that, we are going to use a simple yet extremely useful tool known as Linters.

Linters

Linter is a tool that checks for the typos, code styling and other errors in the code without actually executing it. This process is called static-code analysis. We can find a lot of linters for different languages, which are moreover the same with slight additional features. Nowadays, the linters are so power-packed that they can analyze from documentations to security issues.

Example of Linters:

  • For Python: Pylint, Flake8, etc;
  • For JS: ESLint;
  • For Java: Lint4j etc.

Linter helps us to make the code more readable 👀, removing silly mistakes😅 and save the programmer’s prestige in Code review 👍.

Using Linters with Github Actions

How will Linters with Github actions help us?

We can setup linters to analyze the code in our Github repository and thus helps us find any blunders and follow the standard programming ethics in our code.

What will be the outcome?

A much better and readable code with proper documentation and reduced vulnerabilities of the logical mistakes.

Before initiating the setup, we should have a look at how adding linters in our repository will affect the usual workflow.

To begin the analysis, we will set up a workflow for linting. In this workflow, we will specify the events which will trigger the workflow to execute the linting process. When this setup completes, any one of the specified actions will trigger this workflow automatically. After the workflow execution, we can see the result of the process from the actions tab and also implement changes recommended by the linter.

After this process, our commits will have the status attached to them that shows whether that commit has passed the linting or not.

So, how do we setup these linters in our Github repository?

Since we are using Python, we will use Pylint and Flake8 as linters. After this section, we will also discuss the pros and cons of using multiple linters.

So, going bottom-up in the Github actions hierarchy:

  • We have actions at the root level, in our case, a linting command.
  • One or more actions will constitute a Job, so a collection of linting operations will become a job. We will be using multiple linters, so we could categorize them in different jobs.
  • We will also have jobs to set up our workflow as seen in the above section. These jobs will run prior to linting jobs.
  • These jobs combined will make the workflow for our repository.

So, let’s begin!

Let’s start by creating an empty repository in Github and clone it in our local system. Then create a new directory .github and create another directory inside it with the name workflows. Inside the workflows directory, create a workflow definition with the name python-linter.yml. For this file, we are free to use any name with extension .yml.
Inside this file, i.e. .github/workflows/python-linter.yml, add the following content:

name: Python Lintingon:
push:
branches: [master]
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.6.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8
pip install pylint
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 src --count --max-complexity=10 --max-line-length=79 --statistics
- name: Lint with Pylint
run: |
pylint src

After this, push the code in the Github repository.

The explanation for the above code

  • We have named our workflow Python Linting.
  • In on tag, we have defined the events for which this workflow should be triggered, here push in the master branch or on any pull request is the trigger.
  • We have also included a workflow_dispatch entry in on tag. This will provide us with an option to run this workflow manually on a button click. Consequently, this button will be available in the Actions tab.
  • In the jobs tag, we have specified a job for this workflow. The first two steps of the job will set up our project in the runner, as discussed in the above section.
  • As the name suggests, the next step Install Depenedencies will install the dependencies in the newly created environment of the Runner. First, this will update the pip for the python, and then install pylint and flake for linting. Then it will also install requirements from requirements.txt if that file is present in the root location of our repository.
  • Lint with Flake8 will run as the first step of Linting. In this job, we have defined two actions in which the first one is defined as flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics. This command will execute flake8 for the src folder of our repository. By adding the following options, we have customized the linting process: --count will show the count of all warnings and errors in the result of the command; --select=E9,F63,F7,F82 will limit the checks to syntax errors; --show-source will add the actual line where error/warning is found; and --statistics will show the count of each type of error and warning in the result.
  • Then, we have also added another action in the same job with the definition flake8 src --count --max-complexity=10 --max-line-length=79 --statistics. Unlike the previous command that checks for syntax errors only, this action will run all the available checks on the src folder of our repository.
  • Lint with Pylint step will run the static analysis on our repository using Pylint. This job will run only one action, running pylint on the src folder of our repository.

To test this workflow, we will add a simple python code in the src folder of our repository. We can add any code in our repository. For our example, we will use the following code in the file factorial.py.

def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n-1)
print(factorial(5))

We will now push the code to our remote repository. This action will trigger our workflow, and to verify this, we can see that our commit is listed in the action tab. Based on the timing, the action could be in queued, running, failed, or passed state.

Actions Tab in Github Repo

If the action is in queued or running state, we can wait for some time to complete the execution or browse the job (by clicking on the job) to view the status of the process.

When the workflow completes, the actions will have the status passed or failed. For instance, in the above screenshot, the build job is failed due to some intentional mistakes. We can click on the action to locate where our workflow failed.

After clicking on the above action, this screen will appear

The above screenshot shows that the build job has failed. When we click on the build option, we can see the detailed description.

After clicking, on build job to see where the build process failed

Although our code does not have any syntactical or logical errors, it has linting errors because our code is not self-explanatory.

To resolve the linting errors, we can either remove the workflow errors or add them to the whitelist to ignore them from the next time. As we are adhering to the standard, we will continue by resolving those errors.

So, we will replace the original code with the following code:

"""
This module has the method to calculate factorial of the given number
"""
def factorial(num):
""" Calculates factorial of the passed number
num (int): Integer for which factorial is requested
returns (int): Returns Factorial
"""
if num in (0, 1):
return 1
return num * factorial(num-1)
print(factorial(5))

This code is much more readable and self-explanatory. This code adheres to the PEP standard.

When we push this code, our workflow will run successfully and we can check its status in the Actions tab.

The most recent workflow run will be listed at the top. Here, the green tick represents that it ran successfully.

When to use this workflow?

  • When we have a team project and we want to make our codebase consistent and more readable.
  • Whenever we start with a fresh repository.
  • When collaborations are based on pull requests (PR). This workflow will add a status check for the PR that represents if the code has any linting errors or not. If the check shows failed status, we can work on detected errors before merging the PR.
PR’s will now be processed by the workflow and show its status.

When not to use this worflow ?

  • When we have a large codebase, and we want to integrate this workflow. There is a possibility that we may get tons of errors and warnings. So it is advised to first adjust our codebase according to the standards and then add this workflow.
  • If you are working on a solo project, it is better to follow the linting process manually than adding this workflow.

Points to Remember

  • It is not always feasible to use this workflow, especially for a large codebase. It will cost development time and resources.
  • Usually, customization of linter is required. We can allow linters to skip a few warnings and errors as it may hinder the actual development.
  • Depending on the use case, we can use one or more linter. Using multiple linters makes analysis stricter which may or may not be desired in some of the use cases.
  • Github actions are also used to automatically deploy or test the code, label the pull requests and issues, publish test coverage, create dynamic badges based on building status, and much more.

There’s extensive documentation for the Github actions. So if you’re stuck, just try googling a few keywords and you should be able to find some advice.

Content to Read more

I hope this article proves beneficial to you. In case of any doubts or suggestions, feel free to mention them in the comment section below.

Photo by Pete Pedroza on Unsplash

--

--

Arvind Singh Rawat
The Startup

I am a Backend developer with 4+ years of professional experience building highly performant and scalable backend services.