Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

プログラミングErlang - 例外

3章あたり。

例外の種類

システムが例外を起こすのは内部エラーが発生した場合と
コードが以下のいずれかを意図的に例外を発生させた場合

throw(Exception)

呼び出し側が捕捉する可能性がある例外を投げるのに使う。
この場合、関数が例外を投げる可能性があることをドキュメントに明記すること。

この関数を利用する際には2つの選択肢がある
通常の場合だけを考えてプログラムを書き、例外を知らんぷりするか、
もしくは呼び出しをtry . . . catch 式でくるんでエラーを処理するか。

exit(Exception)

この関数は現在のプロセスを本当に終了させたい場合に使う。
この例外が捕捉されないと、現在のプロセスにリンクされているすべてのプロセスに
{’EXIT’,Pid,Why} メッセージがブロードキャストされる。

eralng:error(Exception)

「クラッシュエラー」を示すために使う。
クラッシュエラーとは、呼び出し側が処理できなさそうなまずい状況をいう。
このエラーは内部的に発生したエラーと同じレベル。

例外を捕捉する方法

  • 関数の呼び出しをtry...catch式でくるむ方法
  • 関数の呼び出しをcatch式でくるむ方法

try..catch

  • afterキーワードの後のコードはFuncOrExpressionSeqの後始末するのに使われる
  • afterは例外が起こった場合でも実行されることが保証される
  • afterセクションは省略できる
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とcatchで結果を比較

catchの方が問題の原因を解析する過程の多く情報がある

・try_test.erl

-module(try_test).
-export([demo1/0, demo2/0]).

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).
    
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, normal, Val}
    catch
        throw:X -> {N, caught, thrown, X};
        exit:X  -> {N, caught, exited, X};
        error:X -> {N, caught, error, X}
    end.

・Eshell

1> c(try_test).
{ok,try_test}

2> try_test:demo1().
[{1,normal,a},
 {2,caught,thrown,a},
 {3,caught,exited,a},
 {4,normal,{'EXIT',a}},
 {5,caught,error,a}]

3> 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,8}]},
              {try_test,'-demo2/0-lc$^0/1-0-',1,
                        [{file,"try_test.erl"},{line,14}]},
              {try_test,'-demo2/0-lc$^0/1-0-',1,
                        [{file,"try_test.erl"},{line,14}]},
              {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,674}]},
              {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...catchを使ったプログラミングスタイル

エラーを返すことが多いコードの場合

「一般的な場合」がない関数ならば{ok, Value} や{error, Reason} のようなものを返す。
ただこの方法だと、呼び出し側は戻り値に対して必ず何らかの対応をしなければならなくなる。

そこで、次に挙げる2つの方法のうちのどちらかを使うべき

...
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_func(X)
catch
    throw:{thisError, X} -> ...
    throw:{someOtherError, X} -> ...
end

ありうる例外をすべて捕捉する

ありうる例外をすべて捕捉したい場合は次のようなイディオムを使う
_が何にでも一致することを利用している

try Expr
catch
    _:_ -> ...例外をすべて処理するコード ..
end

タグを省略して次のように書くと、

try Expr
catch
    _ -> ... 例外をすべて処理するコード...
end

この場合はデフォルトタグとしてthrow が仮定されるから
エラーをすべて捕捉することはできない。

※ 例外を捕捉した場合、erlang:get_stacktrace() を呼び出せば最新のスタックトレースを知ることができる

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment