`
cryolite
  • 浏览: 573296 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

dialyzer: Erlang代码分析器

阅读更多
dialyzer: Erlang代码分析器

Erlang是一种“动态”语言,这会带来一个问题,单元测试不足以证明我写的代码是否足够正确。很难发现动态语言类型错用的问题。静态类型语言倒是很容易找到此类错误,但是Erlang是“动态的”。

例如,length/1函数只能处理类型为列表(list)的参数,如果传入的不是列表,比如传入一个atom就会出错,但是程序中这样的代码是能够通过编译的,运气好的话会有一个警告,运气差的话只能在运行时发现出错。例如以下代码能成功的编译,也不会有警告,但是显然代码是有问题的,这个问题只能在运行时(foo函数被调用时)才能发现:
bar(List) ->
    abc.

foo() ->
    V1 = bar([]),
    io:format("length: ~p~n", [length(V1)]).  %% 这里V1必须是list才行,但是编译时是没法知道的,只有运行时才会发现这个错误


同样的例子,exit/2函数的第一个参数必须是Pid,如果不是也能顺利通过编译,这样只能在运行时才会被发现

此外,我自己写的函数,比如bar函数,可能业务逻辑决定了传入的参数必须是list,返回的参数也应该是list,如果不是,那调用者肯定错误的使用了此函数。在动态语言中很难做到对参数类型和返回类型的限制。

也就是说,对库的接口的错误理解和错误使用是Erlang这样的动态语言常见问题。为此设计了一套合约语言(contract language),有了合约(contract),dialyzer能够很容易的检测到误用的接口

有两种建立合约的方式,一种是在注释里使用@spec这样的annotation,另一种是spec声明
例如规定bar函数只能接收atom或整数,只能返回atom的list:
-type bar_thing() :: atom() | integer().  %% 类型声明:定义bar函数能接收的参数类型
-type ret_thing() :: [atom()].  %% 类型声明:定义bar函数的返回类型

-spec bar(bar_thing()) -> ret_thing(). %% 函数合约:bar函数的参数和返回值

或者
-spec bar(Arg::atom()|integer()) -> [atom()].

另一种是使用annotation的方式,(注意注释中spec要以点号结束annotation,不然无效):
%% @spec bar(Arg::atom()|integer()) -> [atom()].


正如注释一样,annotation不会影响编译。

不过,违反了合约(contact)依然能顺利编译通过,但是我们现在就可以通过dialyzer工具分析源代码找出所有违反合约的代码
dialyzer --src -c test1.erl

可以一次分析工程中的所有文件
dialyzer --src -I ./include -c *.erl
但是可能会有太多的警告信息了,也可以一个文件一个文件的分析,处理起来容易一点

注1:使用dialyzer工具前需要先构建plt:
dialyzer --build_plt -r $OTP_HOME/lib/kernel-2.12.4/ebin\
$OTP_HOME/lib/stdlib-1.15.4/ebin\
$OTP_HOME/lib/mnesia-4.4.5/ebin (或者其它更多的模块)
这一过程耗时很长(大概5分多钟),成功后会在我的home目录下创建一个叫.dialyzer_plt的文件
注2:有个万能类型any()可以代表任意的数据类型;
注3:可以将多个文件中用到的类型(比如pos())集中到一个erl文件中(比如m.erl),通过m:pos()使用该类型;或者将该类型集中到头文件hrl中,使用时包含进来。
注4:typer工具可以列出所有声明的合约

总结:
可以通过确定某种编程规范以及使用Dialyzer这样的工具分析代码是否正确,克服动态语言的弱点。声明和合约一般不影响编译和运行。所以编译通过不一定代表合约有效,还需要dialyzer工具分析

更详细的介绍见
Gradual Typing of Erlang Programs: A Wrangler Experience
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics