脱出構文と例外処理、大域脱出
脱出構文
break | 繰り返しを中断して現在のループを抜ける |
next | 現在の回を飛ばして次の回のループに移る |
redo | 同じ条件でループをやり直す |
例外を発生させる
# 色んな書き方ができるから逆に迷う
raise ArgumentError, 'message'
raise ArgumentError.new, 'message'
raise ArgumentError.new('message')
ArgumentError: message
デフォなら RuntimeErrorが発生する
raise 'message'
RuntimeError: message
例外処理 begin-rescue-end
- 別言語で言う try & catch は存在しない
begin
p 'begin'
rescue
p 'rescue'
else
p 'else'
ensure
p 'ensure'
end
"begin"
"else"
"ensure"
begin
raise
rescue
p 'rescue'
else
p 'else'
ensure
p 'ensure'
end
"rescue"
"ensure"
rescue修飾子
1 / 0 rescue 1
#=> 1
メソッド定義全体を範囲とするならbegin節を省略できるけど、しなくてもいいんじゃないかと思ってる
def moko
1 / 0
rescue
1
end
moko
#=> 1
例外クラスを指定した捕捉
基本的にコード処理で復帰できそうな例外がStandardErrorのサブクラスになっている
この辺りは丸暗記
Exception
ScriptError
SyntaxError 文法エラー
SignalException 補足していないシグナルを受けた
StandardError
ArgumentError 引数の数が合わない、値が正しくない
RuntimeError 特定の例外クラスに該当しないエラー、例外クラスを省略したraise
NameError 未定義のローカル変数や定数を参照した
NoMethodError 未定義のメソッドを呼び出した
ZeroDivisionError 正数に対し、0で除算を行った
- rescueに続いて例外クラスを指定すると、自身かそのサブクラスだけを捕捉することができる
- 例外クラスに続いて => で識別子を指定すると例外オブジェクトを参照できる
ちなみに捕捉例外クラスを指定しない場合は StandardErrorとそのサブクラスがデフォで捕捉対象となる
begin
1 / 0
rescue NameError => e
p e.class
rescue StandardError => e
p e.class
end
例外オブジェクト
- 変数、定数、予約語、演算子関係 $! にて最後に発生した例外オブジェクトにアクセスも可能
- raiseメソッドを引数無しで呼び出すと再度同じ例外を発生させる事も可能(メッセージを出した後で、呼び出し元へ例外を伝える等)
begin
1 / 0
rescue ZeroDivisionError => e
p e.backtrace
p $!.class
raise
end
#=> エラーオブジェクトの情報を出力した後に ZeroDivisionError を発生させる
retry
begin内でretryするともう一度最初のbeginから再実行する
hage = 0
begin
1 / hage
rescue ZeroDivisionError
hage = 1
retry
ensure
p hage
end
1
=> 1
捕捉例外を複数指定する時の注意
最初にマッチしたものしか実行されないので、バカじゃなければマッチする範囲が狭い方から指定する事
hage = 0
begin
1 / hage
rescue
p e.class
raise
rescue ZeroDivisionError
p '1つ目のrescue(StandardError)が必ず補足される為、ここは実行されることはない'
hage = 1
retry
end
hage = 0
begin
1 / hage
rescue ZeroDivisionError
p 'ZeroDivisionErrorの場合にはここが実行されるだろうし、それ以外の例外であれば下のrescueが実行される'
hage = 1
retry
rescue StandardError => e
p e.class
raise
end
catch/throwによる大域脱出
階層の深い中で処理が完了した時など、正常処理中でも処理を抜けたい時等に使うらしいが、正直使ったことがない
- 別言語で言う try & catch は存在しない
- throwがraise、catchがbegin節に相当すると考えるとイイ?
- throwされた場合、catchしてくれる箇所までスタックを駆け上がり、catch中のブロックの後続の処理をスキップする、ような感じの動作をする
- 「ラベル名にはシンボル、文字列が使用できる」と書いてあったけど、ウソ。「同じオブジェクト」が使える。シンボル使っておいてって事
- 対応するラベルが見つからない場合にはNameError例外が発生する
- catchのラベルは省略できるが、throwのラベルは省略できない。つまり、実質的にcatchのラベルを省略する意味がない
catch(:exit1) {
p '実行される'
throw :exit1
p '実行されない!'
}
"実行される"
=> nil
呼び出し先でthrowする スタックを駆け上がる
def moko
throw :exit2
end
catch(:exit2) {
p '実行される'
moko
p '実行されない!'
}
"実行される"
=> nil
throwの第2引数にはcatch節の戻り値を指定する事もできる
def moko
throw :exit3, '戻り値'
end
catch(:exit3) {
p moko
'このハゲー!'
}
=> "戻り値"
# ↑ [p moko] の p も動作していない。catch節の戻り値がirbによって表示されているだけということに注意