Erlang Exception Handling

2009-03-27
Raising an Exception
当系统检测到有错误发生时会自动丢出一个异常,这些异常通常都是由模式匹配错误(没有符合的匹配结果)或传入不当类型的参数到BIFs函数里而引起的。

我们也可以用如下BIFs手动的抛出异常:
exit(Why):当你想终止当前进程的运行时可调用此BIF,如果不捕捉住这个异常,当前进程就会广播{'EXIT, Pid, Why}到所有与其相连的进程。

throw(Why):此BIF用于抛出异常。写程序注释时应注明调用此BIF的函数可能会抛出些什么异常。

erlang:error(Why):此BIF用于抛出一个“崩溃性错误(crashing errors)”,即一些比较奇异的我们没有意料到的错误。

try .... catch
如果你熟悉Java的话,一定知道try...catch系干什么的,Java可以用如下方式来捉住一个异常:
try{
block
} catch(exception type identifier){
block
} catch(exception type identifier){
block
} ....
finally{
block
}

Erlang处理异常的方式也十分相近,就像这样:
try FuncOrExpressionSequence of
Pattern1 [when Guard1] -> Expressions1;
Pattern2 [when Guard2] -> Expressions2;
....
catch
ExceptionType: ExPattern1 [when ExGuard1] -> ExExpressions1;
ExceptionType: ExPattern2 [when ExGuard2] -> ExExpressions2
after
AfterExpressions
end

try...catch的工作方式是这样的:首先执行FuncOrExpressionSeq,如果这部分代码没有引发任何异常,函数就会根据Pattern1、Pattern2....来匹配一个函数返回值,然后返回数据结束运行;BUT如果在执行FuncOrExpressionSeq中跑出了个异常,函数就会跳去匹配ExPattern1、ExPattern2....看看到底是出了什么异常,然后执行相应的ExExpression来处理异常。ExceptionType(throw、exit、error)指明异常的类型,如果忽略的话则默认为throw。注意:由Erlang runtime system检测出的内部错误其异常类型总是error。

跟在after后面的代码一般是用来做清理工作的,即使有异常发生,这部分代码也一定会被执行。

Shortcuts
try...catch表達式的某些部分是可以被忽略的,例如:
try F
catch
....
end

跟下面這段代碼系一樣的:
try F of
Val -> Val
catch
....
end

同樣的,after部分也可以省略掉~~

Programming Idioms with try...catch
我們設計應用程式時,通常都需要保証代碼產生的所有錯誤和异常都會被捕捉到,下面來個例子,首先我們要產生一些异常出來先:
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {'EXIT',a};
generate_exception(5) -> erlang:error(a).

然后我們用try...catch來處理這些异常:
demo1() ->
[catcher(I) || I <- [1,2,3,4,5]].

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

運行上述代碼可得如下結果:
1> try_test:demo1().
[{1, normal, 1},
{2, caught, thrown, a},
{3, caught, exited, a},
{4, normal, {'EXIT', a}},
{5, caught, error, a}]

catch
另外也可以直接使用catch來捕捉异常,如下:
demo2() ->
[{I, (catch generate_exception(I))} || I <- [1,2,3,4,5]].

運行上述代碼可得如下結果:
2> try_test:demo2().
[{1, a},
{2, a},
{3, {'EXIT', a}},
{4, {'EXIT', a}},
{5, {'EXIT', {a, [
{try_test, generate_exception, 1},
{try_test, '-demo2/0-fun-0-', 1},
{lists, map, 2},
{lists, map, 2},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_loop,3}
]}}}]

與try...catch的例子對比,你會發現上例丟失了很多錯誤原因信息~~

Improving Error Messages
erlang:error的一個用途就是改善錯誤信息的提示方式,比如說如果我們在調用函數math:sqrt(X)時傳入一個負數作為參數,會看到如下錯誤提示:
1> math:sqrt(-1).
** exited: {badarith, [
{math, sqrt, [-1]},
{erl_eval, do_apply, 5},
{shell, exprs, 6},
{shell, eval_loop, 3}]} **

羅羅唆唆一大堆又不直觀,我們可以寫如下這樣的代碼來改善一下輸出的信息:
sqrt(X) when X<0 ->
erlang:error({squareRootNegativeArgument, X});
sqrt(X) ->
math:sqrt(X).

2> lib_misc:sqrt(-1).
** exited: {
{squareRootNegativeArgument, -1},
[{lib_misc, sqrt, 1},
{erl_eval, do_apply, 5},
{shell, exprs, 6},
{shell, eval_loop, 3}]} **

Catching Every Possible Exception
如果我們想捕捉晒所有可能出現的錯誤,可以使用如下形式的代碼:
try Expr
catch
_:_ -> ...處理異常...
end

Stack Traces
當捉到異常時,可以通過調用erlang:get_stacktrace()獲取最新的堆棧追蹤信息(stack trace),下面看個例子:
demo3() ->
try generate_exception(5)
catch
error:X ->
{X, erlang:get_stacktrace()}
end.

1> try_test:demo3().
{a, [{try_test, generate_exception, 1},
{try_test, demo3, 0},
{erl_eval, do_apply, 5},
{shell, exprs, 6},
{shell, eval_loop, 3}]}

“堆棧追蹤(stack trace)”包含了一繫列在堆棧上的函數,如果不是因為出了異常,當前函數能順利執行的話,就會返囬到這些函數里去,這些函數的排列順序基本上等于我們進入到當前函數(發生了異常的函數)的函數調用順序(除了尾遞歸函數)。在調試程序的角度來講,stack trace隻有前幾行輸出比較有意義,比如上例的第一行輸出告訴我們繫統崩潰時正在執行函數generate_exception,其參數來自糢塊try_test,try_test:generate_exception/1可能是由try_test:demo3()調用的。

譯自《Programming Erlang》- Chapter 4 - Exceptions

M-OSCAR | Powered by Blogger | Entries (RSS) | Comments (RSS) | Designed by MB Web Design | XML Coded By Cahayabiru.com