When writing code it is good to keep in mind the perspective of the next programmer who will read it, be that yourself or someone else, be that tomorrow or in six months time.

The Elixir/Erlang community has created a variety of tools to improve code maintainability and readability. In this blog I will be showing you how to use four essential tools and also giving my views on them.

A note before I get to the tools, I am a big believer in Test Driven Development and thorough test coverage of code. Elixir has ExUnit, an excellent tool for writing unit tests that run super-fast, all the tools mentioned below should be used on top of thorough test coverage.

Table of Contents

Why listen to me?

My team recently finished a project writing an API in Elixir, where one of the goals was to deliver the project with high maintainability. Below I will be discussing tools that we used to increase maintainability in our production code.

ExDoc

This is the tool used to generate all of the official documentation in Elixir. It is also the tool used to generate documentation for packages published to Hex, the most widely used package manager in the Elixir/Erlang ecosystem. Its goal is to make generating documentation for your project as easy as possible, and I think it does a good job of meeting that goal.

Adding it to your project

Add the following code to your mix.exs file:

def deps do
  {:ex_doc, "~> 0.13", only: :dev}
end

More detailed installation and configuration instructions can be found on the Github page.

How to create documentation

If you go straight to generating documentation now, you will see that ExDoc has done an excellent job of creating a skeleton structure of your project and its modules for you to add more information to.

The way you will be adding documentation is by adding attributes to your modules. For example:

defmodule MyProject.Response do
  @moduledoc """
  This module will respond to a greeting.
  """
end

This uses the @moduledoc attribute to add documentation at the module level.

There is also the @doc attribute which adds documentation for a single function. You may find when using this attribute with functions that use pattern matching and multiple function definitions it can get a little messy. In order to get around this you can define a body-less definition of your function to add the @doc attribute too. For example:

defmodule MyAnimals.Sound do

  @doc """
  Recognises an animal given its sound.
  """
  def recognise(sound)

  def recognise("oink") do
    :pig
  end

  def recognise("moo") do
    :cow
  end
end

Besides adding useful documentation to your project, ExDoc also has a killer feature, DocTest. This feature allows you to run unit tests on the examples you give of how to use your functions. Here is an example of how to do that:

defmodule MyMaths.Basic do

  @doc """
  This function will add two numbers together.

  ## Examples

      iex> MyMaths.Basic.add(3, 4)
      7

  """
  def add(num_one, num_two) do
    num_one + num_two
  end
end

defmodule MyMaths.BasicTest do
  use ExUnit.Case, async: true
  doctest MyModule
end

As you can see in this example we include iex> in the @doc attribute on a function to describe an example of how to use a function. Then, in the corresponding test file we use the doctest macro to run tests on those examples.

The idea of DocTest is to compliment documentation by ensuring the examples given in it are up-to-date and correct. It should not be seen as a substitute to unit tests. Also, just because your unit tests pass, it does not mean your documentation examples are up-to-date.

Elixir provides more information on writing documentation on their website.

How to generate documentation

In root directory of your project run mix docs, then the documentation will be accessible at “doc/index.html”.

For more options run mix help docs.

Dialyzer / Dialyxir

Another attribute you can add to your functions is @spec, which allows you to define the types that the inputs and outputs of the function will be. This gets added to pages created by ExDoc and allows the user to get a better understanding of how to use the function. For example:

@spec multiply(integer, integer) :: integer
def multiply(a, b) do
  a * b
end

Elixir provides more information on Typespecs on their website.

But how can you test the correctness of the types you specify? This is where Dialyxir, powered by Dialyzer comes in to play.

Adding it to your project

Add the following code to your mix.exs file:

defp deps do
  [{:dialyxir, "~> 0.3", only: :dev}]
end

How to run the analysis

Before the first time you use dialyxir, you will have to run mix dialyzer.plt, unfortunately this takes a while, but speeds up subsequent runs.

Once you do that, just run with mix dialyzer. Unfortunately, the output from dialyzer leaves a lot to be desired, however you should be able to get some useful information from it. A tip I have picked up is to pipe the output into grep if you are looking for output from a particular file.

Credo

Credo is a code analysis tool with an emphasis on consistency within your code base. It can tell you things like whether you have been consistent with either tabs or spaces, whether you are about to commit any TODO statements, or whether a function probably has too many arguments, and many more.

The display of the output is a real highlight, and greatly helps in understanding how to fix the issues it raises.

Adding it to your project

Add the following code to your mix.exs file:

def deps do
  {:credo, "~> 0.4", only: [:dev, :test]}
end

More detailed instructions on installation can be found on the Github page.

Configuration

Credo can be configured to include/skip certain directories and checks, even set up different profiles.

Configuration is written in a file called .credo.exs which can live in the root directory of the project or in the config/ folder. An example config file.

How to run the analysis

In the root directory of your project just run mix credo. I would recommend running it with the --strict argument to get more feedback.

ExCoveralls

ExCoveralls is a tool to analyse code coverage on a project. It can provide command-line output, but also integrate with continuous integration tools such as Travis CI and Gitlab CI.

I particularly enjoy trawling through the output using the coveralls.io service which is free for public repositories.

Adding it to your project

Add the following code to your mix.exs file:

def project do
  [...
   test_coverage: [tool: ExCoveralls],
   preferred_cli_env: ["coveralls": :test]]
end

defp deps do
  [{:excoveralls, "~> 0.5", only: :test}]
end

How to run the analysis

In the root directory of your project just run mix coveralls.

Summary

I hope this post has been helpful in your discovery of tools to use for improving maintainability. I encourage you to click through the links I have provided in this post in order to learn more about the tools I mention above.

Lastly, I’d like to thank all the authors of the tools above for their contributions to the Elixir community.