エラーは起こるものです。Elixirでは、よく「クラッシュさせておけ」と言われますが、ソフトウェアの契約を果たすために、関数呼び出しを既知の正常な状態にまで捕捉しなければならないこともあります。言語によっては、エラーを制御フローの手段として使いますが、Elixirではこのパターンは推奨されません。エラーを発生させる可能性のある関数は、名前を見ればたいてい見分けられます。エラーを発生させる関数は、名前に!が付いています。これは、{:ok, value}や:errorを返す関数と対比できます。ライブラリの例を見てみましょう。
Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError
Elixirには、try .. rescueを使ってエラーから捕捉するための構文があります。
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
この構文を見てみましょう。
tryでブロックを宣言します。raise/2を呼び出しています。rescueセクションでは、発生したエラーの_モジュール_名に対してパターンマッチを行います。
->の左側では、
eがエラー構造体にマッチします。inはキーワードです。RuntimeErrorは、捕捉したいエラーです。
_を使うか、inキーワードを完全に省略できます。このようにして捕捉するエラー(「例外」と呼ばれることもあります)は、構造体です。 Elixirでエラーを捕捉することは、めったにありません。 通常は、捕捉したエラーをログに記録したり、外部の監視サービスに送信したりしたあと、もう一度発生させます。 つまり、特定のエラー構造体の内部構造を気にする必要は、通常ありません。
例外のコンセプトでは、エラー構造体についてさらに詳しく学べます。独自のカスタムエラーを定義する方法も含まれます。
Instruments of Texas で働くあなたは、Elixirで書かれた実験的な逆ポーランド記法[RPN]の計算機の開発を任されました。チームでは、一部の演算でエラーが発生してプロセスがクラッシュするという問題が起きています。そこで、演算関数をラップし、Elixirらしいコードでよりエレガントにエラーを処理できる関数を書くことになりました。
calculate!/2関数を実装して、スタックだけを引数として演算関数を呼び出します。演算関数は別の場所で定義されていますが、正常に完了するかエラーを発生させるかのどちらかであることがわかっています。
stack = []
operation = fn _ -> :ok end
RPNCalculator.calculate!(stack, operation)
# => :ok
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate!(stack, operation)
# => ** (ArgumentError) An error occurred
名前が
!で終わる関数は、その関数がエラーを発生させる可能性があることをプログラマーに警告するものです
さらに調べていると、多くの関数がアトムとタプルを使って成功・失敗を表していることに気づきます。この方法でcalculate/2を実装しましょう。
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate(stack, operation)
# => :error
エラーの中には、システムが正しく動作するために同僚が知っておく必要のある重要な情報が含まれています。エラーメッセージを伝えるためにcalculate_verbose/2を実装しましょう。エラーは:messageフィールドを持つ構造体です。
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:error, "An error occurred"}