読者です 読者をやめる 読者になる 読者になる

もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

Elixir 入門 その18 - try, catch and rescue

  • 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の構造はプロセスが失敗したら再開されることを保証するからである。