例外処理¶
Crystalのエラー処理方法は、例外を発生させて捕捉することです。
例外の発生¶
トップレベルの`raise`メソッドを呼び出すことで例外を発生させます。他のキーワードとは異なり、`raise`は、文字列を受け入れるものと、例外インスタンスを受け入れるものの2つのオーバーロードを持つ通常のメソッドです。
raise "OH NO!"
raise Exception.new("Some error")
文字列バージョンは、そのメッセージを持つ新しいExceptionインスタンスを作成するだけです。
`Exception`インスタンスまたはサブクラスのみを発生させることができます。
カスタム例外の定義¶
カスタム例外型を定義するには、Exceptionからサブクラスを作成するだけです。
class MyException < Exception
end
class MyOtherException < Exception
end
いつものように、例外のコンストラクタを定義することも、デフォルトのコンストラクタを使用することもできます。
例外の捕捉¶
任意の例外を捕捉するには、`begin ... rescue ... end`式を使用します。
begin
raise "OH NO!"
rescue
puts "Rescued!"
end
# Output: Rescued!
捕捉された例外にアクセスするには、`rescue`句に変数を指定します。
begin
raise "OH NO!"
rescue ex
puts ex.message
end
# Output: OH NO!
1つのタイプの例外(またはそのサブクラス)のみを捕捉するには、次のようにします。
begin
raise MyException.new("OH NO!")
rescue MyException
puts "Rescued MyException"
end
# Output: Rescued MyException
有効な型制限は、`::Exception`のサブクラス、モジュール型、およびこれらの共用体です。
そして、それにアクセスするには、型制限と同様の構文を使用します。
begin
raise MyException.new("OH NO!")
rescue ex : MyException
puts "Rescued MyException: #{ex.message}"
end
# Output: Rescued MyException: OH NO!
複数の`rescue`句を指定できます。
begin
# ...
rescue ex1 : MyException
# only MyException...
rescue ex2 : MyOtherException
# only MyOtherException...
rescue
# any other kind of exception
end
共用体型を指定することで、複数の例外タイプを一度に捕捉することもできます。
begin
# ...
rescue ex : MyException | MyOtherException
# only MyException or MyOtherException
rescue
# any other kind of exception
end
else¶
`else`句は、例外が捕捉されなかった場合にのみ実行されます。
begin
something_dangerous
rescue
# execute this if an exception is raised
else
# execute this if an exception isn't raised
end
`else`句は、少なくとも1つの`rescue`句が指定されている場合にのみ指定できます。
ensure¶
`ensure`句は、例外が発生したかどうかに関係なく、`begin ... end`または`begin ... rescue ... end`式の最後に実行されます。
begin
something_dangerous
ensure
puts "Cleanup..."
end
# Will print "Cleanup..." after invoking something_dangerous,
# regardless of whether it raised or not
または
begin
something_dangerous
rescue
# ...
else
# ...
ensure
# this will always be executed
end
`ensure`句は、通常、クリーンアップ、リソースの解放などに使用されます。
短縮構文¶
例外処理には短縮構文があります。メソッドまたはブロック定義を暗黙の`begin ... end`式と見なし、`rescue`、`else`、および`ensure`句を指定します。
def some_method
something_dangerous
rescue
# execute if an exception is raised
end
# The above is the same as:
def some_method
begin
something_dangerous
rescue
# execute if an exception is raised
end
end
`ensure`付き
def some_method
something_dangerous
ensure
# always execute this
end
# The above is the same as:
def some_method
begin
something_dangerous
ensure
# always execute this
end
end
# Similarly, the shorthand also works with blocks:
(1..10).each do |n|
# potentially dangerous operation
rescue
# ..
else
# ..
ensure
# ..
end
型推論¶
例外ハンドラの`begin`部分内で宣言された変数は、`rescue`または`ensure`本体内で考慮される場合、`Nil`型にもなります。例えば
begin
a = something_dangerous_that_returns_Int32
ensure
puts a + 1 # error, undefined method '+' for Nil
end
`something_dangerous_that_returns_Int32`が例外を発生させない場合、または`a`に値が代入されてから、例外が発生する可能性のあるメソッドが実行される場合でも、上記が発生します。
begin
a = 1
something_dangerous
ensure
puts a + 1 # error, undefined method '+' for Nil
end
`a`には必ず値が代入されますが、コンパイラは`a`が初期化されない可能性があると判断します。このロジックは将来改善される可能性がありますが、現時点では、例外ハンドラを必要最小限のものに保ち、コードの意図をより明確にする必要があります。
# Clearer than the above: `a` doesn't need
# to be in the exception handling code.
a = 1
begin
something_dangerous
ensure
puts a + 1 # works
end
エラー処理の代替方法¶
例外はエラー処理のメカニズムの1つとして利用できますが、唯一の選択肢ではありません。例外を発生させるにはメモリを割り当てる必要があり、例外ハンドラの実行は一般に低速です。
標準ライブラリは通常、何かを実行するための2つのメソッドを提供します。1つは例外を発生させ、もう1つは`nil`を返します。例えば
array = [1, 2, 3]
array[4] # raises because of IndexError
array[4]? # returns nil because of index out of bounds
通常の規則では、メソッドのこのバリアントが例外を発生させる代わりに`nil`を返すことを示すために、代替の「質問」メソッドを提供します。これにより、ユーザーは例外を処理するか、`nil`を処理するかを選択できます。ただし、例外はエラー処理ロジックでコードを汚染しないため、依然として好ましい方法であるため、これはすべてのメソッドで利用できるわけではないことに注意してください。