Fork me on GitHub

エラーハンドリング

{:error, reason}のようなタプルを返すのが一般的とはいえ、Elixirは例外をサポートしており、このレッスンではエラーハンドリングの方法や利用可能な異なる仕組みについて見ていきます。

一般的に、Elixirでは{:ok, result}{:error, reason}を返す関数(example/1)を作成する、あるいはラップされていないresultを返すかエラーを発生させる関数(example!/1)に分離するのが慣習です。

このレッスンでは後者に焦点を当てます。

目次

エラーハンドリング

エラーをハンドリングする前にエラーを発生させる必要がありますが、最も簡単な方法はraise/1を使うことです:

iex> raise "Oh no!"
** (RuntimeError) Oh no!

もしエラーの種類やメッセージを明記したいならraise/2を使う必要があります:

iex> raise ArgumentError, message: "the argument value is invalid"
** (ArgumentError) the argument value is invalid

エラーが起き得ることが分かっている場合、try/rescueとパターンマッチングを使ってエラーをハンドリングできます:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> end
An error occurred: Oh no!
:ok

単独のrescue節で複数のエラーにマッチさせることができます:

try do
  opts
  |> Keyword.fetch!(:source_file)
  |> File.read!
rescue
  e in KeyError -> IO.puts "missing :source_file option"
  e in File.Error -> IO.puts "unable to read source file"
end

After

時にはエラーの有無にかかわらずtry/rescueの後に何らかの処理を必要とする場面があるかもしれません。このような場合のためにtry/afterが存在します。これはRubyのbegin/rescue/ensureやJavaのtry/catch/finallyに似ています:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> after
...>   IO.puts "The end!"
...> end
An error occurred: Oh no!
The end!
:ok

これはファイルや接続が必ず閉じられなければいけない場合に最もよく使われます:

{:ok, file} = File.open "example.json"
try do
   # Do hazardous work
after
   File.close(file)
end

新しいエラー

ElixirにはRuntimeErrorのように多くの備え付けのエラー型が含まれていますが、プロジェクト特有のエラー型が必要なら新しいエラーを作ることが出来ます。新しいエラーを簡単に作るには、デフォルトのエラーメッセージを指定するために:messageオプションを受け入れるdefexception/1マクロを使います:

defmodule ExampleError do
  defexception message: "an example error has occurred"
end

私たちの新しいエラーを試してみましょう:

iex> try do
...>   raise ExampleError
...> rescue
...>   e in ExampleError -> e
...> end
%ExampleError{message: "an example error has occurred"}

Throw

Elixirのエラーを扱うためのもう一つのメカニズムはthrowcatchです。実際には新しいElixirコードでこれらを使うのは非常にまれですが、にも関わらずこれらを知り理解することは重要です。

throw/1関数を使えばcatchできる特定の値で実行を中断できます:

iex> try do
...>   for x <- 0..10 do
...>     if x == 5, do: throw(x)
...>     IO.puts(x)
...>   end
...> catch
...>   x -> "Caught: #{x}"
...> end
0
1
2
3
4
"Caught: 5"

すでに述べたように、throw/catchはかなり珍しく、概してライブラリが十分なAPIを提供していない時の間に合わせとして存在します。

終了

Elixirが提供している最後のエラー処理機構はexitです。終了シグナルはプロセスが死に、それがElixirのフォールトトレランスの重要な部分であるときに発せられます。

明示的に終了するにはexit/1を使います:

iex> spawn_link fn -> exit("oh no") end
** (EXIT from #PID<0.101.0>) "oh no"

try/catchで終了を捕捉できますが、そうすることは 非常に まれです。ほとんど全ての場合ではsupervisorにプロセスの終了をハンドリングさせるほうが都合がいいでしょう:

iex> try do
...>   exit "oh no!"
...> catch
...>   :exit, _ -> "exit blocked"
...> end
"exit blocked"

このページをシェアする