Erlang 顺序程序的错误处理

Erlang 最初被设计用来编写容错式系统,这种系统原则上应该永不停歇。这就意味着运行时的错误处理是至关重要的。

处理顺序代码里的错误

每当我们在 Erlang 里调用某个函数后,返回或者返回一个值,或者出现了问题。

%% shop.erl
cost(oranges)   -> 5;
cost(newspaper) -> 8;
cost(apples)    -> 2;
cost(pears)     -> 9;
cost(milk)      -> 7.

运行函数发生的现象:

1> c(shop).

{ok,shop}

2> shop:

cost/1 module_info/0 module_info/1

2> shop:cost(apples).

2

3> shop:cost(socks).

** exception error: no function clause matching shop:cost(socks) (shop.erl, line 4)

当我们调用了 cost(socks) 后程序崩溃了,发生这种情况的原因是函数定义里没有任何一个子句能匹配调用的参数。

异常错误发生于系统遇到内部错误时,或者通过在代码里显式调用 throw(Exception)、exit(Exception) 或 error(Exception) 触发。会触发异常错误的调性内部错误有模式匹配错误,用错误类型的参数调用内置函数,以及用带有错误值的参数调用内置函数。

可以通过调用下面的某个内置函数来显式生成一个错误。

exit(Why) 当你确实像要终止当前进程时就用它。

throw(Why) 抛出一个调用者可能想要捕捉的异常错误。

error(Why) 它与系统内部生成的错误差不多。

Erlang 有两种方法来捕捉异常错误。第一种是把抛出异常错误的调用函数封装在一个 try...catch 表达式里,另一种是把调用封装在一个 catch 表达式里。

用 try...catch 捕捉异常错误

语法如下:

try FuncOrExpressionSeq of
    Pattern1 [when Guard1] -> Expressions1;
    Pattern2 [when Guard2] -> Expressions2;
    ...
catch
    ExceptionType1: ExPattern1 [when ExGuard1] -> ExExpressions1;
    ExceptionType2: ExPattern2 [when ExGuard2] -> ExExpressions2;
    ...
after
    AfterExpressions
end

try...catch 具有一个值

Erlang 里一切都时表达式,而表达式都具有值。因此我们可以编写这样的代码:

f(...) ->
...
X = try ... end,
Y = g(X),
...

更多情况下不需要 try...catch 表达式的值。所以只需要这么写:

f(...) ->
    ...
    try ... end,
    ...
    ...

try...catch的工作方式如下: 首先执行 FuncOrExpessionSeq。如果执行过程没有抛出异常错误,那么函数的返回值就会与 Pattern1、Pattern2 等模式进行匹配,直到匹配成功。如果能匹配,那么整个 try...catch 的值就通过执行匹配模式之后的表达式序列得出。

如果 FuncOrExpressionSeq 在执行中抛出了异常错误,那么 Expattern1 等捕获模式就会与它进行匹配。ExceptionType 是一个原子(throw、exit 和 error),它告诉我们异常错误是如何产生的。如果省略了 ExceptionType 就会使用默认值 throw。

关键词 after 之后的代码是用在 FuncOrExpreesionSeq 结束后执行清理的。这段代码一定会被执行,那么有异常抛出也是如此。after 区块的代码会在 try 或 catch 区块里的 Expression 代码执行完成后立即执行。AfterExpressions 的返回值会被丢弃。

简写法

可以省略 try...catch 表达式的多个部分。

try F
catch
    ...
end

就等于这一段:

try F of
    Val -> Val
catch
    ...
end

除此之外,after 部分也可以省略。

try...catch编程样例

设计应用程序时,如果某段代码的作用是捕捉错误,那么通常会设法确保它能捕捉到函数可能产生的一切错误。

下边这两个函数对此进行了演示。第一个函数会产生三种不同类型的异常错误,并有两个常规的返回值。

%% try_test.erl
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {‘EXIT‘, a};
generate_exception(5) -> error(a).

现在封装一个函数,用它在一个 try...catch 表达式里调用 generate_exception。

demo1() ->
    [catcher(I) || I <- [1,2,3,4,5]].

catcher(N) ->
    try generate_exception(N) of
        Val -> {N, nomal, Val}
    catch
        throw:X -> {N, caught, thrown, X};
        exit:X  -> {N, caught, exited, X};
        error:X -> {N, caught, error, X}
    end.

generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {‘EXIT‘, a};
generate_exception(5) -> error(a).

它展现了捕捉与区分一个函数所能抛出的所有异常错误形式。

1> c(try_test).

{ok,try_test}

2> try_test:demo1().

[{1,nomal,a},

{2,caught,thrown,a},

{3,caught,exited,a},

{4,nomal,{‘EXIT‘,a}},

{5,caught,error,a}]

用 catch 捕捉异常错误

另一种捕捉异常错误的方法是使用基本语法 catch。catch 语法和 try...catch 里的 catch 区块不是一回事。异常错误如果发生在 catch 语句里,就会被转换成一个描述此错误的 {‘EXIT‘, ...} 元组。

%% try_test.erl
demo1() ->
    [catcher(I) || I <- [1,2,3,4,5]].

demo2() ->
    [{I, (catch generate_exception(I))} || I <- [1,2,3,4,5]].

catcher(N) ->
    try generate_exception(N) of
        Val -> {N, nomal, Val}
    catch
        throw:X -> {N, caught, thrown, X};
        exit:X  -> {N, caught, exited, X};
        error:X -> {N, caught, error, X}
    end.

generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {‘EXIT‘, a};
generate_exception(5) -> error(a).

两种方法提供了不同量级的调试信息。

1> try_test:demo2().

[{1,a},

{2,a},

{3,{‘EXIT‘,a}},

{4,{‘EXIT‘,a}},

{5,

{‘EXIT‘,{a,[{try_test,generate_exception,1,

[{file,"try_test.erl"},{line,23}]},

{try_test,‘-demo2/0-lc$^0/1-0-‘,1,

[{file,"try_test.erl"},{line,8}]},

{try_test,‘-demo2/0-lc$^0/1-0-‘,1,

[{file,"try_test.erl"},{line,8}]},

{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,673}]},

{shell,exprs,7,[{file,"shell.erl"},{line,686}]},

{shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},

{shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}}}]

针对异常错误的编程样式

改进错误消息

内置函数 error/1 的一种用途是改进错误消息的质量,如果在调用 math:sqrt(X) 时提供了一个负值的参数,就会发生下面的情况:

1> math:sqrt(-1).

** exception error: an error occurred when evaluating an arithmetic expression

in function math:sqrt/1

called as math:sqrt(-1)

编写一个封装函数来改进错误消息。

%% lib_misc.erl
sqrt(X) when X < 0 ->
    error({squareRootNegativeArgument, X});
sqrt(X) ->
    math:sqrt(x).

1> lib_misc:sqrt(-1).

** exception error: {squareRootNegativeArgument,-1}

in function lib_misc:sqrt/1 (lib_misc.erl, line 5)

经常返回错误时的代码

如果你的函数并没有什么通用的情形,那么多半应该返回 {ok, Value} 或 {error, Reason} 这类值,但是请记住,这将迫使所有的调用者必须对返回值做点什么。

第一种:

...
case f(X) of
    {ok, Val} ->
        do_some_thing_with(val);

    {error, Why}  ->
        %% ... 处理这个问题 ...

end,
...

第二种:

...
{ok, Val} = f(X),
do_some_thing_with(val);
...

如果 f(X) 返回 {error, ...} 就会抛出一个异常错误。

错误可能有但罕见时的代码

这种情况下,通常要写能处理错误的代码,就像这个例子一样:

try my_fun(X)
catch
    throw:{thisError, X} -> ...
    throw:{someOtherError, X} -> ...
end

同时,检测错误的代码也应该带有匹配的 throw,就像这样:

my_func(X) ->
    case ... of
        ...
        ... ->
            ... throw({thisError, ...})
        ... ->
            ... throw({someOtherError, ...})

捕捉一切可能的异常错误

如果想要捕捉一切可能的错误,就可以使用下面的句式:

try Expr
catch
    _:_ -> ... 处理所有异常错误的代码 ...
end

如果在代码里漏写了标签:

try Expr
catch
    _ -> ... 处理所有异常错误的代码 ...
end

就不会捕捉到所有错误,因为在这种情形下系统会假设标签是默认的 throw。

栈跟踪

捕捉到一个异常错误后,可以调用 erlang:get_stacktrace() 来找到最近的栈跟踪信息。

demo1() ->
    [catcher(I) || I <- [1,2,3,4,5]].

demo2() ->
    [{I, (catch generate_exception(I))} || I <- [1,2,3,4,5]].

demo3() ->
    try generate_exception(5)
    catch
        error:X ->
            {X, erlang:get_stacktrace()}
    end.

catcher(N) ->
    try generate_exception(N) of
        Val -> {N, nomal, Val}
    catch
        throw:X -> {N, caught, thrown, X};
        exit:X  -> {N, caught, exited, X};
        error:X -> {N, caught, error, X}
    end.

generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {‘EXIT‘, a};
generate_exception(5) -> error(a).

1> try_test:demo3().

{a,[{try_test,generate_exception,1,

[{file,"try_test.erl"},{line,30}]},

{try_test,demo3,0,[{file,"try_test.erl"},{line,11}]},

{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,673}]},

{shell,exprs,7,[{file,"shell.erl"},{line,686}]},

{shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},

{shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}

上面的跟踪信息展示了试图执行 try_test:demo3() 时发生了什么。它表明程序在 generate_exception/1 函数中崩溃。

栈跟踪还抱哈了当前函数如果执行成功会返回何处的信息。栈跟踪里的各个元组都是 {Mod, Func, Arity, Info} 这种形式。Mod、Func 和 Arity 指明了某个函数,Info 则包含了栈跟踪里这一项的文件名和行号。

如果某个函数在表达式序列中被调用,那么调用位置和函数将要返回的位置几乎是一样的。如果被调用的函数表达式序列的最后一个函数,那么此函数的调用位置信息不回保留在栈上。Erlang 回对这一类代码进行尾调用优化,因此栈跟踪信息不会记录被调时的位置,只会记录它将要返回的位置。

分析栈跟踪信息能让我们很好的判断出错误发生时程序的执行位置。通常栈跟踪信息的头两天就足以让你找到错误发生的位置了。

永远不要在函数被错误参数调用时返回一个值,而是要抛出一个异常错误。要假定调用者会修复这个错误。

抛出错误的原则

第一、应该在错误发生时立刻将它抛出,而且要抛的明显。错误消息应当被写入永久性的错误日志,而且要包含足够多的细节,以便过后查看是哪里出了错。

第二、只有程序员才应该看到程序崩溃时产生的详细错误消息。程序的用户绝对不能看到这些消息。

时间: 2024-10-29 19:07:13

Erlang 顺序程序的错误处理的相关文章

用 rebar 来构建、编译、测试、发布 Erlang 应用程序

转自:http://dhq.me/build-compile-eunit-release-erlang-application-with-rebar rebar 是一个遵循 Erlang/OTP 原则的 Erlang 项目构建工具,使用它可以减少构建标准 Erlang/OTP 项目架构配置的工作量,并且可以很容易的编译.测试.发布 Erlang 应用程序.更强大的是,rebar 提供一种依赖管理机制,它可以使开发者很方便地通过 Git.Hg 等方式重用常见的第三方 Erlang 模块或库. 安装

SxsTrace程序追踪 &amp;&amp; 错误信息分析

先贴错误:应用程序无法运行,并行配置不正确 ,使用命令行sxstrace.exe.百度解决版本. 起因:同事给我一 EXE,然后基于 其进行开发 dll和模块,但是无法加入进程,无法运行. SxsTrace使用 1.程序无法运行, sxstrace.exe进行追踪. 1.测试本地命令能否成功执行. cmd 下,任意目录,c:\> sxstrace 回车: 2.转(cd)至程序所在目录,运行命令:SxsTrace Trace -logfile:SxsTrace.etl,启动跟踪: 3.运行程序(可

erlang 小程序:整数序列,搜索和为正的最长子序列

最近学习了一下erlang, 编了个小程序 算法如下: 把参数分为三个 当前位置的前子序列(Save)(比如 -5, 1,2,-1, _, ... ) 前位置为_时, 前子序列就是 1,2,-1 以及此子序列的和(CurSum) ( 1,2,-1的 CurSum 是 2) 剩余的数 Rest:  剩余的数也可以表示为 [H|T] ,H是第一元素,T为剩余的元素是列表 处理过程如下: 如果 Rest 为空,说明处理完毕,打印 CurSum和Save 如果,还剩一个元素(Rest只有一个元素) 如果

JAVA程序调试错误集

这边文章是我记录调试JAVA程序的错误,每次遇到都会更新! 错误1.HTTP Status 405 - HTTP method GET is not supported by this URL 解决办法:删除super.doGet(request, response); 错误2. HTTP Status 404 - /Simple/GetAddress ------------------------------------------------------------------------

SpringBoard 无法启动应用程序(错误:-3)

暂时不知道错误的细节原因,重启模拟器就好了. 先记录下. SpringBoard 无法启动应用程序(错误:-3),布布扣,bubuko.com

配置错误:未能使用提供程序“RsaProtectedConfigurationProvider”进行解密。提供程序返回错误信息为: 打不开 RSA 密钥容器。

http://www.cnblogs.com/jiaruistone/articles/1441634.html 我们如果想对web.config的数据库连接字符串进行加密的话,那么这里提供了两个方法. 方法一.     使用“DataProtectionConfigurationProvider”形式加密,创建test.aspx文件,代码如下: 需要添加引用 using System.Web.Configuration; using System.IO; //加密 protected void

串口协议匹配函数,避免串口数据接收时顺序换乱错误

按照协议匹配,避免串口数据接收时顺序换乱错误. 包头 长度 地址码 回复状态 校验和 包尾 备注 C0C0 02 F5 AA YY CF 成功 1,转移字符 a)              数据包基本格式中的数据长度.数据和校验和中如果出现关键字C0. CF或CA则需要在其前端加上转义字符CA,即将数据C0.CF或CA发送成CAC0.CACF或CACA,将数据C0C0发送成CAC0CAC0. b)              数据包基本格式中的数据长度length以有效数据的数量为准,即不 需要也

Python Cookbook(第3版)中文版:14.12 调试基本的程序崩溃错误

14.12 调试基本的程序崩溃错误? 问题? 你的程序崩溃后该怎样去调试它? 解决方案? 如果你的程序因为某个异常而崩溃,运行 python3 -i someprogram.py 可执行简单的调试.-i 选项可让程序结束后打开一个交互式shell.然后你就能查看环境,例如,假设你有下面的代码: # sample.py def func(n): return n + 10 func('Hello') 运行 python3 -i sample.py 会有类似如下的输出: bash % python3

C# net core程序调试错误集(持续更新)

目录 C#程序调试错误集 1.依赖注入错误An unhandled exception has occurred while executing the request. 1.1 出错现象 1.1.1原因是net core在调用ValueController的时候,发现UnitOfWork没有进行依赖注入. 1.2 出错现象 1.2.1 原因是net core在调用UnitOfWork的时候,发现IPBoxContext没有进行依赖注入. 1.3 解决方法 C#程序调试错误集 1.依赖注入错误A