Enforcing your Elixir code to be well formatted, with no warnings and hopefully free of bugs. I’m talking about a mix alias that will check the quality of the code, and that can be used during local development and also on CI/CD pipelines to enforce the team (or yourself) to keep a clean code. But keep in mind that there's no magic, you're still responsible for creating a code that's well organized, performant, without security issues, and pleasant for human reading.

Table of Contents

TL;DR

The complete example is at the end of the article.

Tools

Credo and Dialyzer

Credo is responsible for checking if the code is adherent to common good code practices established by the community. And dialyzer is a static analysis tool that identifies software discrepancies, such as definite type errors, code that has become dead or unreachable because of a programming error (paraphrasing the official docs), but we'll use dialyxir, which is wrapper around dialyzer written in Elixir that simplify the use of dialyzer.

Sobelow

Sobelow is a security-focused static analysis tool for the Phoenix framework. If you're not using Phoenix, just remove it from the deps, configs, and alias described on this article.

mix format

The mix format task was introduced in Elixir v1.6, and it's used to format your code automatically. One of the main benefits of using it is to avoid boring and endless discussions like "how we should format the code ?", "what should be the line length ?", and so on. Elixir also uses it to enforce a standard format.

Warning as errors

That’s not a tool, actually, it’s an Elixir compile attribute that will treat warnings in the current project as errors and return a non-zero exit code, which means that your project won’t compile if it has any warning.

A note before we continue

Having all those tools and configs turned on may be annoying. A module without doc, a wrong spec or even an unused variable will stop you from compiling and shipping the code. The level of enforcement is up to you, so you should adapt the tools and configs as required for your project (and your stress level). But it's a good ideia to plan for the long term.

Implementation

Ok, let's change the files to implement the quality check. Let’s do that piece by piece, or better saying, function by function.

mix.exs

First, let's install the deps. Add the following in deps list:

{:credo, "~> 1.0", only: [:dev, :test], runtime: false},

{:dialyxir, "~> 1.0.0-rc.6", only: [:dev, :test], runtime: false},

{:sobelow, "~> 0.7", only: [:dev, :test], runtime: false}

And let’s change the project’s config. Add this to the project function:

elixirc_options: [warnings_as_errors: true],

aliases: aliases(),

dialyzer: [

plt_file: {:no_warn, "priv/plts/dialyzer.plt"},

ignore_warnings: ".dialyzer_ignore.exs"

]

Which will be like:

I recommend reading dialyxir doc to know more about its config, especially the Continuous Integration if you want to enforce it on your CI/CD pipeline.

Continuing, let's create an alias function in your mix.exs file, if you don't have this function already created, and add two aliases:

I like using one alias specific for local development (quality) and one for CI/CD (quality.ci) because I have more freedom on the order of the tasks and its arguments. For example, on local development I don't want to check if the code is formatted, I just format the code directly; and I like to see test results at the end. Adapt as you want.

.dialyzer_ignore.exs

Create the file .dialyzer_ignore.exs in the root dir of the project:

Remember I said those tools may be annoying ? Specially the dialyzer. Don't get me wrong, dialyzer is amazing and I believe you should use it, but sometimes you need to ignore a warning or two, and that's the file where you can do that.

About the content of this file: we'll run our aliases on the test environment (MIX_ENV=test) and dialyzer reclaim that those functions are errors, but that's not a big deal and let's just ignore it.

Credo

You don’t need to change anything in order to make credo work, but the default rules may not be suitable for your project. You can change that using a .credo.exs file or using special comments.

.gitignore

Add to .gitignore:

# Dialyzer

/priv/plts/*.plt

/priv/plts/*.plt.hash # Sobelow

.sobelow

Those files are environment-dependent.

Executing

Finally, you're ready to go! 😅

First, let's create the dir where dialyzer will store plt files:

mkdir -p priv/plts

And then just execute in your terminal:

MIX_ENV=test mix quality

Take some time off and grab a coffee, the first execution will take some time to build all dialyzer artefacts, but those files will be cached, don’t worry.

And change your CI/CD pipeline to execute:

MIX_ENV=test mix quality.ci

Whenever this command finds an issue in your code, the CI/CD pipeline will halt and return a failure.

Complete example files

mix.exs

.dialyzer_ignore.exs

.gitignore

References