Best practice for defining functions with edoc,erlc,eunit and the dialyzer
Joe Armstrong
erlang@REDACTED
Wed Dec 2 10:28:58 CET 2009
Best practice for writing documenting and testing code
I'd like to try and define "best practice" for writing documenting and
testing Erlang code. I want to use:
- only the tools supplied in the OTP release
So I use:
- eunit for unit testing
- the dialyzer for checking my code
- edoc for documenting things
- type specifications for specifying types
These tools do not completely "play together" in a satisfactory manner,
so I'd like to define what I thing is "best practice" and hope that by doing
so the tools will converge.
Let's suppose I want to define the good 'ol factorial. Here's a module
called elib1_best.erl. I've written it in such a way that it can be
processed by erlc,eunit,edoc and the dialyzer - read the footnotes
in brackets for an explanation.
-module(elib1_best). %% [1]
%% elib1_best: Best practice template for library modules [2]
%% Time-stamp: <2009-12-02 09:43:12 ejoearm> [3]
%%----------------------------------------------------------------------
%% Copyright (c) 2009 Joe Armstrong <erlang@REDACTED> [4]
%% Copyright (c) 2009 Whoomph Software AB
%%
%% Permission is hereby granted, free of charge, to any person
%% obtaining a copy of this software and associated documentation
%% files (the "Software"), to deal in the Software without
%% restriction, including without limitation the rights to use, copy,
%% modify, merge, publish, distribute, sublicense, and/or sell copies
%% of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be
%% included in all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%% SOFTWARE.
%%-----------------------------------------------------------------------
-include_lib("eunit/include/eunit.hrl"). %% [5]
-export([fac/1]). %% [6]
%% @doc fac(N) computes factorial(N) using a fast
%% iterative algorithm. [7]
-spec fac(integer()) -> integer(). [8]
fac(N) when is_integer(N), N >= 0 -> fac1(N, 1).
fac1(0, K) -> K;
fac1(N, K) -> fac1(N-1, K*N).
fac_test() -> %% [9]
6 = fac(3),
24 = fac(4).
%% Notes:
%% [1] - module on line 1
%% [2] - module comment
%% [3] - Time stamp auto generated by emacs.
%% Must be near start of file
%% [4] - Copyright (I always forget this, but adding a
%% copyright reduces the pain later
%% [5] - Needed for eunit
%% [6] - use export and NOT compile(export_all)
%% [7] - @doc comes first
%% [8] - -spec comes immediately *before* the function
%% [9] - test cases come immediately after the function
%% end of module
...
Now let's see what happens:
1) Compiling
erlc +warn_missing_spec -o ../ebin -W elib1_best.erl
./elib1_best.erl:0: Warning: missing specification for function test/0
./elib1_best.erl:44: Warning: missing specification for function fac_test/0
Best practice is to support type specifications for all exported
functions. But eunit magically adds a function test/0 and I really
don't want to have to add manual exports and type specs for
fac_test/0.
[A fix is needed to erlc here, OR eunit can add type specs,
I think the latter is better - erlc should not need to know about eunit]
2) Dialyzing
dialyzer --src elib1_best.erl
Checking whether the PLT /home/ejoearm/.dialyzer_plt is up-to-date... yes
Proceeding with analysis...
Unknown functions:
eunit:test/1
done in 0m0.32s
done (passed successfully)
This is ok - I could add eunit to my plt if I wanted ...
the dialyzer warns for missing functions so I don't need to run
xref
3) Documentation
I'll run edoc on everything in the current directory putting the
results in ../doc
> erl -noshell -eval "edoc:application(lib, \".\", [{dir,\"../doc\"}])" \
-s init stop
This works fine and ../doc/elib1_best.html has the documentation
but now edoc has not found my nice -spec declaration and thinks that
fac has type: fac(N) -> any()
Why: because edoc and erlc don't use the same type parser.
Current best practice is to use -spec (in code) and
not @spec (in edoc comments)
[A fix is needed here to edoc, to understand -spec's]
4) Testing
1> eunit:test(elib1_best, [verbose]).
======================== EUnit ========================
elib1_best: fac_test (module 'elib1_best')...ok
=======================================================
Test passed.
ok
Great ...
Now for questions.
1) Does this represent best practice? Is this the best way to
write code? - can anybody improve on this?
[And yes I know about quickcheck, but I'm only concerned
with SW in the OTP release]
2) If I write like this can I assume that one day edoc
and eunit and erlc will converge so that I get correctly displayed
types in edoc and no warnings in erlc etc?
3) Does anything else have to be fixed?
4) Improvements..
I can think of one. I have some code to convert .erl to
.html with correctly colored code and hyperlinks etc.
So I can "surf" the code. It would be nice to have hooks
into edoc so I can call this code
That's all for now ...
/Joe
More information about the erlang-questions
mailing list