Skip to content

Latest commit

 

History

History
133 lines (94 loc) · 4.38 KB

11_best_practices.asciidoc

File metadata and controls

133 lines (94 loc) · 4.38 KB

How to Write code to take advantage of Dialyzer

Warning
This section is a work in progress, feel free to send corrections etc as pull request. Please Use advice here with caution

Dialyzer is great at finding problems in existing code, but it seems that if we are cleaver in how we write our code then dialyzer could be even more powerful in helping us find bad code. And as Dialyzer can work on the existing code this could save us time in terms of not having to write explicit tests and so on.

The question is how to best do this, I think this is an open topic, but I expect a number of common sense rules will help us make sense of it.

  • Fix Warnings

  • Use Spec’s everywhere

  • Use specific types

  • Use Opaque Types

  • Write Short Functions

  • Compose Functions

Lets go threw those in order

Fix all warnings

There are many things that the compiler or other tools will warn you about (such as a variable that is not used). It is a good practice to fix all of these. You can tell the compiler to treat warnings as errors to force you to do so. If they are in a library then fix them and send a pull request.

Use Spec’s everywhere

You should add -spec() lines to all of your functions and types to all of your records. Of course Dialyzer can try to deduce types for you, but it will work better if you are explicit.

Use Specific types

Your "Customer ID" value may just be a binary, but if you give it the type customer_id() vis binary() you have the distinct advantage that it can’t cross contaminate with other things that might have the same underlying type. This is the same idea that was done in days past with Hungarian Notation, where you would use a naming system to keep different variables separate.

Even better would be instead of having customer_id() be an alias for binary() would be to have it have the signature {'customer_id', binary()} this way you can pattern match on it and have dialyzer positively identify it.

Use Taged types

Instead of passing a simple data to a function passing the data to the function as a taged type has a lot of advantages. First of all the function can pattern match on its input to prevent the wrong inputs being passed.

It also allows dialyzer to be more specific with checking for impedence mismatches.

Tagged Function
find_word(Board = {boggle_board, _}) ->
   ....

Use Opaque types

Use of Opaque types can also help with program correctness by allowing a type to be only accessed in one module.

TODO EXPAND

TODO Examples

Use Short functions

Keeping functions short helps with just about everything. TODO Expand

Compose type transformers

By creating a number of smaller functions that can be reused to transform data it is possible to create simple tests that will test those functions and dialyzer can make sure that they are put together in a sane way.

TODO: Example

Split Pure and Impure code

One can improve code quality if you take a page from the Haskell playbook. By splitting the code that deals with IO including the database, WebInterface or the like from the logic that transforms the data it functions will be easier to test and to type check.

Understand what can be caught by what

This book has shown numbers of different kinds of tests, unit tests, Integration tests, property tests, static checks and more. Each of these can best capture some types of errors, it is important to understand which ones can best help you find a problem. It is not a good deal to spend hours writing unit tests for things that dialyzer can find for you automaticly, nor should you write a unit test that tests one case and proclaim yourself done, create a property based test to really exercise the function.

Testing library code

Most modern projects will rely on a number of external libraries that have been pulled down from github or other sources. You may use JSX for parsing JSONs or webmachine to create a web api. The question is how much should you test these yourself and how much should you just assume that they will work.

For my regular test runs I generally will run rebar with skip_deps=true if nothing else this prevents my build from taking up time running the same tests on the same code each time I make a small change.

Practice

Doing TDD, and even more so TDD with properties has a learning curve, it requires a real effort to do it well and to make sure it is done every time.