Introducing Dialyzer & type-specs to an Elixir Project
Tags: elixir, dialyzer, types
Published: 2020-03-17 14:35:45

---
title: Introducing Dialyzer & type-specs to an Elixir Project
published: true
description: Some simple steps for a little more type safety in Elixir
tags: elixir, dialyzer, types
---

At [Contact Stack](https://www.contact-stack.com) we use [Elixir](https://elixir-lang.org/) & [Phoenix](http://www.phoenixframework.org/) as the core of the backend. I am fascinated by the design & power of the BEAM, the virtual machine that Elixir targets, and I want to learn more about it. I also love strongly & statically typed languages, particularly due to my recent history working with [Elm](https://elm-lang.org/) which has a great type system and a helpful compiler.

Elixir is dynamically typed language which leaves me a little nervous about the long term future of the project. I really like types and I find them incredibly useful when the code base grows larger and you're returning to parts of it that you haven't looked at in a while. Often I can't remember exactly the kind of data that I'm passing around and strong types really help with that understanding. Plus I've grown to love having a compiler guide you through the code base when doing refactoring as it points out all the places you need to change.

One way to bring a little more type safety to Elixir projects is to use [Dialyzer](http://erlang.org/doc/man/dialyzer.html). Dialyzer performs static analysis on your code and tries to warn you about anything that is almost certainly going to fail. It can be helped by providing type annotations and it also derives its own from inspecting the code. It isn't as good as a solid type system but it is better than nothing.

So, how do we go about introducing Dialzyer and type annotations through out our codebase? Here are the steps that I've followed for Contact Stack.

## Install Dialyzer

Maybe a bit of an obvious first step! We can use the [dialxyir](https://github.com/jeremyjh/dialyxir) project to add a dialyzer task to mix. At the time of writing that involves adding this line to your `mix.exs` `deps`:
```
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
```

## Run Dialyzer & Fix issues

We can now run dialyzer with:
```
mix dialyzer
```
On the command line. This can take a long time as dialyzer analyses and creates `PLT` caches for all the Elixir & Erlang standard libraries. Fortunately it is much quicker on that next run though still not as fast a many compilers out there.

The errors can honestly be quite hard to understand. I would recommend googling as much as possible. I've learned a few of things though that I'll share.

Firstly, in my projects I've had to config dialyzer to make it away of `ex_unit` and `mix` otherwise it issues warnings. I suspect I only needed to add `mix` because I have a custom mix task. You can configure dialyzer by adding the following to your the `project` config in mix.exs: 
```
dialyzer: [plt_add_apps: [:ex_unit, :mix], ignore_warnings: "config/dialyzer.ignore"]
```
Here we add `ex_unit` & `mix` and also indicate an ignore file.

Secondly, some errors don't seem to be clearly fixable by code that is under our control. In which case we want to add those errors to the ignore file. If you run dialyzer with:
```
mix dialyzer --format dialyzer
```
Then you can copy the start or all of the error that you want to ignore onto a new line in the ignore file we've just set up and dialyzer will skip it on future runs. 

Thirdly, if you get a warning that says that some function has `no local return` then it normally means there is another warning within the function itself that makes dialzyer think that the function will never work. You can skip the 'no local return' warning at look at the next one in the output so see what the issue might be.

## Introduce Credo

Credo is a linting tool for Elixir that can warn if you don't provide a type annotation on a function. Part of our goal here is to make sure that we provide type annotations for all the functions in our code base. This helps Dialyzer understand our intent and is self-documenting which helps ourselves and others on the code base.

So we add:
```
{:credo, "~> 1.2", only: [:dev, :test], runtime: false}
```
To our `mix.exs` `deps`.

## Configure & Fix Credo Warnings

Credo checks for a lot of different things. It does not check for type annotations by default and we're going to enable that but first, as long as they are manageable, fix any current warnings that Credo has in place. You can do this by changing your code or suppressing some of Credo's warnings. I have the following config for my preferences:

```
%{
  configs: [
    %{
      name: "default",
      files: %{
        included: ["lib/"],
        excluded: []
      },
      checks: [
        {Credo.Check.Design.AliasUsage, false},
        {Credo.Check.Readability.AliasOrder, false}
      ]
    }
  ]
}
```

## Extend Credo Rules to Include Type Annotations

We extend our config to include the `Specs` check by adding this line:

```diff
      checks: [
        {Credo.Check.Design.AliasUsage, false},
        {Credo.Check.Readability.AliasOrder, false}
+       {Credo.Check.Readability.Specs, []}
      ]
```

## Silence the Huge Number of Credo Warnings

Now we can run credo again to get a sense of the number of missing type annotations. If you have a small number then go ahead and try to add them now. If, like me, you have something like 200 warnings or more then you might prefer to silence them for now and attend to them gradually.

To do this we can run the following command:
```
mix credo --strict | grep lib | cut -d " " -f 8 | cut -d ":" -f 1 | sort -u | xargs sed -i '1s/^/# credo:disable-for-this-file Credo.Check.Readability.Specs\n/'
```
This is going to add `# credo:disable-for-this-file Credo.Check.Readability.Specs` to the top of every file in your codebase that has missing type annotations.

When we run `mix credo --strict` again we should see a clean sheet. Now you can go into the files at your leisure and remove the comment at the top and add type annotations. Credo will warn about any that are missing and warn by default for any new files we add to the system.

## Final Thoughts

I worry that dialyzer is not very useful in comparison to the type systems & compilers that I have used before but it feels good to be trying to make the code as tight and error free as possible. I am at the beginning of this journey so I can't really report on whether it is worth it long term but we'll see.