Erlang最初被设计用来编写容错式系统,这种系统原则上应该永不停歇。
常见的Erlang应用程序是由几十万到几百万个并发程序组成的,拥有大量进程改变了我们对错误处理的看法,在并发性的大程序中,单个进程的崩溃就没有那么重要了,前提是其他某些进程能够察觉到这个崩溃,并接手崩溃进程原本应该做的事情。
要完全了解错误处理,必须先了解顺序程序里的错误处理,理解之后再来看如何处理大量并行进程里的错误。
1. 处理顺序代码里的错误
防御式编程:永远不能让函数对非法的参数返回值,而是应该抛出一个异常错误。
Erlang中常见的内置函数来显示生成一个错误。
exit(Why)
当你确实想要终止当前进程时就用它。如果这个异常错误没有被捕捉到,信号{‘EXIT’, Pid, Why}就会被广播给当前进程链接的所有进程。
throw(Why)
这个函数的作用是抛出一个调用者可能想要捕捉的异常错误。在这种情况下,我们注明了被调用函数可能会抛出这个异常错误。有两种方法可以代替他使用:可以为通常的情形编写代码并且有意疏忽异常错误,也可以把调用封装在一个try…catch表达式里,然后对错误进行处理。
error(Why)
这个函数的作用是指示“崩溃性错误”, 也就是调用者没有准备好处理的非常严重的问题。
它与系统内部生产的错误差不多。
Erlang有两种方法,来捕捉异常错误。第一种是把抛出异常错误的调用封装在一个try…catch表达式里,另一种是把调用封装在一个catch表达式里。
2. 用try…catch捕捉异常错误
Erlang捕捉异常例子:
try FuncOrExpressionSeq of
Pattern1 [when Guard1] -> Expressions1;
Pattern2 [when Guard1] -> Expressions2;
...
catch
ExceptionType1:ExPattern1 [when ExGuard1] -> ExExpressions1;
ExceptionType2:ExPattern2 [when ExGuard2] -> ExExpressions2;
...
after
AfterExpressions
end
注意:Erlang里的一切都是表达式,而表达式都具有值。因此,我们可以这样编写代码:
f(...) ->
...
X = try...end,
Y = g(X),
...
更多情况下,并不需要try…catch表达式的值。所以只需要这样写:
f(...) ->
...
try...end,
...
...
请注意try…catch表达式和case表达式之间具有相似性,try…catch就像是case表达式的强化版。他基本上是case表达式加上了最后的catch和after区块。
try…catch编程样例:
-module(test_try).
-export([generate_exception/1, demo1/0, catcher/1]).
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {'EXIT', a};
generate_exception(5) -> error(a).
demo1() ->
[catcher(I) || I <- [1,2,3,4,5]].
catcher(N) ->
try generate_exception(N) of
Val -> {N, normal, Val}
catch
throw:X -> {user, N, caught, thrown, X};
exit:X -> {user, N, caught, thrown, X};
error:X -> {user, N, caught, thrown, X}
end.
3. 用catch捕捉异常错误
异常错误如果发生在catch语句中,就会被转换成一个描述此错误的{‘EXIT’, …}元组。
catch错误会提供详细的栈跟踪信息。
4. 练习
(1).file:read_file(File) 会返回 {ok, Bin} 或者 {error, Why},其中 File 是文件名, Bin 则包含了文件的内容。请编写一个myfile:read(File) 函数,当文件可读取时返回 Bin,否则抛出一个异常。
-module(test_file_read).
-export([file_read/1]).
file_read(File) ->
try file:read_file(File) of
{ok, Content} -> Content;
{error, Reason} -> throw(Reason)
catch
throw:X -> io:format("throw Reason is: ~p ~n", [X]);
error:X -> io:format("throw Reason is: ~p ~n", [X]);
exit:X -> io:format("throw Reason is ~p, ~n", [X])
end.
(2)重写 test_try.erl 里的代码,让它生成两条错误消息;一条明文消息给用户,另一条详细的消息给开发者。
-module(test_try).
-export([generate_exception/1, demo1/0, catcher/1]).
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {'EXIT', a};
generate_exception(5) -> error(a).
demo1() ->
[catcher(I) || I <- [1,2,3,4,5]].
catcher(N) ->
try generate_exception(N) of
Val -> {N, normal, Val}
catch
throw:X -> {user, N, caught, thrown, X},
{developer, N, erlang:get_stacktrace()};
exit:X -> {user, N, caught, thrown, X},
{developer, N, erlang:get_stacktrace()};
error:X -> {user, N, caught, thrown, X},
{developer, N, erlang:get_stacktrace()}
end.
5. 小结
异常处理在Erlang中是非常重要的,只有先理解并熟悉了简答的顺序程序的错误处理过程,才能帮助以后学习并发性程序的错误处理。
为错误编写代码时要考虑两个关键的原则:
1. 应该在错误发生时立即将它抛出,而且要抛出的明显;
2. 要文明抛出。文明抛出的意思是指,只有程序员才能看到该程序崩溃时产生的详细错误信息,用户绝对不可以看到这个信息。