Erlang學習筆記(六)順序程式的錯誤處理

畫船聽雨發表於2017-08-28

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. 要文明丟擲。文明丟擲的意思是指,只有程式設計師才能看到該程式崩潰時產生的詳細錯誤資訊,使用者絕對不可以看到這個資訊。

相關文章