Ruby学習 「プロを目指す人のためのRuby入門」 9章

はじめに

プロを目指す人のためのRuby入門Rubyを学習しています。
重要そうな部分や理解が難しかった部分、忘れそうなことについて学習メモを書きます。
今回は第9章「例外処理を理解する」について書いていきます。

記事中のプログラムはirbで動作を確認しました。

例外の補足

  • 例外発生時に例外を補足し、処理を継続したい場合は例外処理を記述する
## ソースファイルに下記処理を記述し、rubyコマンドで実行
puts 'Start'

begin
  1 / 0
rescue
  puts '例外発生'
end

puts 'End'

## 以下は実行結果
=>Start
=>例外発生
=>End
  • 例外が発生した場合、その時点で処理を中断し、メソッド呼び出しを1つずつ戻っていき、途中で例外を補足していればそこから処理を続行する

  • Rubyでは例外もオブジェクトであり、例外オブジェクトのメソッドで例外に関する情報を取得できる

  • classメソッドはエラークラスを返す
  • messageはエラーメッセージを返す
  • backtraceはバックトレース情報を配列で返す
begin
  1 / 0
## 「=> e」は例外オブジェクトを変数eに格納するという意味
rescue => e
  puts "エラークラス: #{e.class}"
  puts "エラーメッセージ: #{e.message}"
  puts "バックトレース:  #{e.backtrace}"
end

エラークラス: ZeroDivisionError
エラーメッセージ: divided by 0
バックトレース:  ["(irb):22:in `/'", "(irb):22:in `irb_binding'", "/hom
以下、長いので略
  • 例外は種類ごとにクラスが違い、補足する例外クラスを指定することができる
begin
  1 / 0
rescue ZeroDivisionError, NoMethodError => e
  puts "error: #{e.class} #{e.message}"
end
  • すべての例外クラスはExceptionクラスを継承しており、大きく分けてStandardErrorクラスのサブクラスと特殊なエラークラスに分かれる
  • rescue節にクラスを指定しなかった場合にはStrandardErrorクラスとそのサブクラスが補足される
  • rescue節にクラスを指定した場合はそのクラスとそのクラスのサブクラスが補足される
  • rescue節にStandardErrorと無関係の例外クラスやExceptionクラスを指定するのは避ける
  • rescue節は複数指定できるが、例外クラスの継承関係を意識した順番にする必要がある
  • rescue節でretry文を実行すると、begin節の最初からやり直す
  • retry文はネットワークエラー等、一時的な問題が例外の原因の場合に有効だが、無限ループになる可能性があるためカウンタを用意して回数を制限する
  • raiseメソッドを使用すると例外を発生させることができる
raise ZeroDivisionError, "0で除算しました。"
ZeroDivisionError: 0で除算しました。
  • 例外処理の範囲と補足する例外クラスの種類はできるだけ絞り込む
  • 安易に例外処理を使用せず、事前に問題が発生しないかチェックする方法がないか確認する
  • 複数条件の条件分岐では、通常あり得ない条件については例外を発生させることを検討する
  • 例外処理で、例外が発生してもしなくても実行したい処理がある場合はensure節を使用する
  • rescue節は必須ではないので、異常終了させるが、終了前に実行したい処理がある場合はensure節だけ使用することも可能
  • ファイルの入出力を行う場合は、openメソッドにブロックを渡すことで、ブロックの実行中に例外が発生してもopenメソッドがクローズ処理を実行してくれる
  • openメソッド以外にも上記のような処理を自動的に実行するメソッドがある
  • 例外処理で、例外が発生しなかった場合に実行したい処理がある場合はelse節を使用する
begin
  # 例外が発生する可能性がある処理
rescue
  # 例外が発生した場合の処理
else
  # 例外が発生しなかった場合の処理
ensure
  # 例外が発生してもしなくても実行する処理
end
  • else節の中で実行された処理はエラーが発生しても手前にあるrescue節で補足されない
  • 例外処理では、例外が発生しなかった場合はbegin節の最後の式が戻り値になり、例外が発生してrescue節に補足された場合はrescue節の最後の式が戻り値になる
  • ensure節でreturnを使用すると正常時、例外発生時ともにensure節の値が戻り値になってしまうためensure節ではreturnは使用しないようにする
  • rescueは修飾子としても使用可能
  • rescueを修飾子として使用した場合は、補足する例外クラスを指定できず、StandardErrorとそのサブクラスが補足される
1 / 0 rescue 0
=> 0
1 / 1 rescue 0
=> 1
  • 最後に発生した例外は組み込み変数の$!に格納され、バックトレース情報は$@に格納される
  • メソッドの中身全体が例外処理の対象となっている場合はbeginとendを省略可能
def zero_divide
  1 / 0
rescue
  puts '0で除算しました。'
end

=> :zero_divide

zero_divide
0で除算しました。
=> nil
  • rescue節やensure節の処理に問題があると、そこでも例外が発生して異常終了する場合があるのでrescue節、ensure節の処理も必ず動作確認する
  • rescue節やensure節で例外が発生した場合、Exceptionクラスのcauseメソッドでもともと発生していた例外情報を取得可能
  • resuce節でraiseメソッドを引数なしで実行すると、rescue節で補足した例外を再度発生させることができる
  • rescue節での引数なしraiseメソッドは、異常終了はさせるが、例外情報をログに残したりメールで送信したい場合に使用する
  • 独自の例外クラスを定義することができる
  • 独自の例外クラスは、特別な事情がない限り、StandardErrorクラスかそのサブクラスを継承させる(すべての例外を補足してしまうのでExceptionクラスを直接継承しない)

検討事項

  • 例外オブジェクトは$!に、バックトレースは$@に格納されるとのことだが、$!にバックトレースを持っていないのか ‘‘‘Ruby begin 1 / 0 rescue puts "#{$!.class} #{$!.message}" puts "$!.backtrace #{$!.backtrace}" puts "$@ #{$@}" end ZeroDivisionError divided by 0 $!.backtrace ["(irb):2:in /'", "(irb):2:inirb_binding'",

    バックトレースを省略

    $@ ["(irb):2:in /'", "(irb):2:inirb_binding'",

    バックトレースを省略

begin 1 / 0 rescue puts "#{$!.class.instance_methods}" end [:==, :respond_to?, :inspect, :to_s, :exception, :message, :backtrace, :backtrace_locations, :set_backtrace, :cause, :instance_of?, :kind_of?, :is_a?, :tap, :public_send, :remove_instance_variable, :instance_variable_set, :method, :public_method, :singleton_method, :extend, :define_singleton_method, :to_enum, :enum_for, :<=>, :===, :=~, :!~, :eql?, :freeze, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variable_get, :instance_variables, :instance_variable_defined?, :!, :!=, :send, :equal?, :instance_eval, :instance_exec, :id] => nil

結果から$!.backtraceでもバックトレースを取得可能。
$!は例外クラスのオブジェクトであり、Exceptionクラスを継承しているため、<br/>
backtraceメソッドを持っている。



## 感想
Rubyの例外処理の形式や使いどころが理解できた。<br/>
実装の際には不要な例外処理を使用しないように意識していく。<br/>
Javaで言うExceptionはRubyで言うStandardErrorクラスであることを忘れないようにする。<br/>


## 参考文献

この記事は以下の情報を参考にして執筆しました。

- [プロを目指す人のためのRuby入門](https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%92%E7%9B%AE%E6%8C%87%E3%81%99%E4%BA%BA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AERuby%E5%85%A5%E9%96%80-%E8%A8%80%E8%AA%9E%E4%BB%95%E6%A7%98%E3%81%8B%E3%82%89%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%83%BB%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E6%8A%80%E6%B3%95%E3%81%BE%E3%81%A7-Software-Design-plus%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA/dp/4774193976/)