- Elixirは errors throws exits の3つのErrorに関する仕組みを持つ
- ただ、Erlangでは基本的に「クラッシュさせるならさせておけ」というポリシーがあるのでどのような場合にErrorをハンドリングするかは考えるべき
Errors
- ErrorあるいはExceptionはコードの中で想定外のことが起きた場合に使われる
# 例えばatomとintegerを計算しようとすると算術例外が起きる
iex> :foo + 1
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(:foo, 1)
# raiseを使えばランタイムで意図的に例外を起こせる
iex> raise "oops"
** (RuntimeError) oops
# defexception で自前の例外を定義できる
defmodule MyError do
defexception message: "default message"
end
# 例外の名前はモジュール名が表示される
iex> raise MyError
** (MyError) default message
iex> raise MyError, message: "custom message"
** (MyError) custom message
- try-catch的な仕組みもある。Elixirではcatchはrescueという名前
iex> try do
...> raise "oops"
...> rescue
...> e in RuntimeError -> e
...> end
%RuntimeError{message: "oops"}
- ただ、Elixirではあまりこの仕組みは使わない。例えばファイルを開く場合、成功した場合は
:ok
を含んだtupleを、なんらかの理由で失敗した場合は :error
を含んだtupleがファイルオープンの関数の結果として返される。
- 標準ライブラリの多くの関数は成功・失敗の情報を含んだtupleを返す。
- つまり、パターンマッチを使って関数の結果を見て判定できる
iex> case File.read "hello" do
...> {:ok, body} -> IO.puts "Success: #{body}"
...> {:error, reason} -> IO.puts "Error: #{reason}"
...> end
- なので標準のライブラリのAPIを使っている場合、ほとんどの場合においてtry-throw-rescueの仕組みは使わない。APIでカバーできない場合に初めて使うことを考慮すべき。
after
を使うと必ず実行される。try-catchにおけるfinallyのようなもの。
- 例えばファイルを開いたのちに書き込む処理で、ファイルが開かれた後に削除される可能性があるのであれば、以下のように例外をケアしたほうがよいだろう
iex> {:ok, file} = File.open "sample", [:utf8, :write]
iex> try do
...> IO.write file, "olá"
...> raise "oops, something went wrong"
...> after
...> File.close(file)
...> end
** (RuntimeError) oops, something went wrong
Exits
- すべてのElixirのコードは相互に通信しあいながら動く。unhandled exceptionなどでプロセスが死ぬ場合、そのプロセスはexit signalを送る。同様に開発者が明示的にexit signalを送ることもできる。
# spawnで新しく作成したプロセスが明示的にexitを送る例
iex> spawn_link fn -> exit(1) end
#PID<0.56.0>
** (EXIT from #PID<0.56.0>) 1
- Exit signalは Erlang VMから受け継いだ、フォールトレトラントなシステムの重要な部分である。プロセスは通常、
supervised processes
からのexitを待つsupervision trees
の下で動く。いったん supervised process
から exit
を受け取ったら、それを監督するprocessは、そのprocessをkickしてrestartする。(ちょっとここ訳怪しい)
- この仕組みの下で動くElixir(というかErlang)ではプロセスが失敗したら早く終わらしたほうが良いということになる。なぜなら
supervision tree
の構造はプロセスが失敗したら再開されることを保証するからである。