連載
» 2014年10月31日 18時00分 公開

若手エンジニア/初心者のためのRuby 2.1入門(9):Rubyの例外とその捕捉――基本のbegin〜rescue〜endからensure、else、retry、後置rescueまで (3/4)

[著:麻田優真、監修:山根剛司,株式会社アジャイルウェア]

例外を「捕捉」して処理する

 ここまで、例外の仕組みの概要と、例外を発生させる方法について紹介しました。ここでは、例外を捕捉して処理する方法について解説します。

基本形「begin〜rescue〜end」

 例外を捕捉するための基本形を、exception02.rbに示します。

begin
  raise "something wrong"
rescue => e
  puts "An exception is occurd!"
  p e
  p $!
end
exception02.rb
$ ruby exception02.rb 
An exception is occurd!
#<RuntimeError: something wrong>
#<RuntimeError: something wrong>
exception02.rbの実行結果

 例外を捕捉したいコードを「begin〜rescue」の間に記述し、例外が起きた場合の処理を「rescue〜end」の間に記述します。また、「rescue => e」のように記述することで、rescue〜end間で変数eに例外オブジェクトが格納されます。また、グローバル変数「$!」を参照することで例外オブジェクトを扱うこともできます。

 ここでは、2行目でraiseメソッドを使ってRuntimeErrorを発生させているので、実行結果の3行目でRuntimeErrorと、raiseメソッドに設定したメッセージ「something wrong」が出力されていることが分かります。

例外クラスを指定して捕捉する

 基本形で紹介した方法では、例外クラスの種類に応じて処理を振り分けたい場合、rescue〜end間でif文などを使って、例外オブジェクトのクラスを参照して処理を分岐させる必要があります。実は、そのようなことをしなくても、exception03.rbのように記述すれば、例外オブジェクトのクラスに応じて処理を振り分けられます。

require_relative "exception01"
 
begin
  teapot = Teapot.new("coffee")
  puts teapot.pour_out
rescue UnacceptableRequidError => e
  puts "Rescued in 'UnacceptableRequiedError => e'"
  p e
  p e.obj
rescue NoMethodError, ZeroDivisionError => e
  puts "Rescued in 'rescue NoMethodError, ZeroDivisionError => e'"
  p e
end
exception03.rb
$ ruby exception03.rb 
Rescued in 'UnacceptableRequiedError => e'
#<UnacceptableRequidError: unacceptable>
"coffee"
exception03.rbの実行例

 require_relativeでexception01.rbのコードを利用するので、exception03.rbはexception01.rbと同じディレクトリに配置してください。

 例外クラスに応じて処理を振り分ける場合は、6行目や9行目のように、rescueに続けて例外クラスを指定します。また、10行目のように複数の例外クラスを指定することもできます。

 ここでは、コードの4行目で、Teapotが許容しない「coffee」でTeapotオブジェクトを生成しようとしています。UnacceptableRequidError自体はTeapotの基底クラス、Vesselのpour_inメソッドの中で発生します。しかし、pour_inメソッドの中では例外を捕捉しないため、コールスタック(呼ばれたメソッドを順々に積んだスタック)をさかのぼっていき、最終的にexception03.rbに記述されているbegin〜end節で捕捉されます。

 この例では「Teapot.new("coffee")」で例外が発生するため、5行目は実行されず7行目に処理が移ります。Rubyのインタプリタは複数記述されたrescueを上から順番にチェックし、初めて合致した例外クラスの例外処理に分岐させるため、11〜12行目の処理は実行されません。

 また、exception01.rbではUnacceptableRequidErrorで@objというインスタンス変数を設定したことを思い出してください。そのため、コードの9行目と実行結果の4行目のように、どのようなオブジェクトを設定しようとして例外が発生したのかを知ることができます。

 もし、rescueに設定されたどの例外クラスにも合致しない場合、exception04.rbのように、例外の捕捉が行われないのでプログラムはその場で停止し、エラーメッセージが出力されます。

require_relative "exception01"
 
begin
  bottle = Bottle.new("water")
  bottle.pour_out
rescue UnacceptableRequidError => e
  puts "Rescued in 'UnacceptableRequiedError => e'"
  p e
rescue NoMethodError, ZeroDivisionError => e
  puts "Rescued in 'rescue NoMethodError, ZeroDivisionError => e'"
  p e
end
exception04.rb
$ ruby exception04.rb 
exception04.rb:4:in `<main>': uninitialized constant Bottle (NameError)
exception04.rbの実行結果

 ここでは4行目で、定義されていないBottleクラスを使おうとしています。Bottleクラスは定義されていないので、NameErrorという例外が発生して処理が止まってしまいます。シチュエーションによっては、これでは困ることもあるでしょう。そのような場合、exception05.rbのように、StandardErrorを指定して捕捉するとよいでしょう。

require_relative "exception01"
 
begin
  bottle = Bottle.new("water")
  bottle.pour_out
rescue UnacceptableRequidError => e
  puts "Rescued in 'UnacceptableRequiedError => e'"
  p e
rescue NoMethodError, ZeroDivisionError => e
  puts "Rescued in 'rescue NoMethodError, ZeroDivisionError => e'"
  p e
rescue StandardError => e # rescue => e でもOK
  puts "Rescued in 'rescue StandardError => e'"
  p e
end
exception05.rb
$ ruby exception05.rb 
Rescued in 'rescue StandardError => e'
#<NameError: uninitialized constant Bottle>
exception05.rbの実行結果

 exception04.rbの記述に加え、12〜14行目にStandardErrorを捕捉するrescueを記述しています。例外クラスの合致判定を行うとき、発生した例外クラスの基底クラスも対象とします。NameErrorはStandardErrorから派生しているため、無事12行目で捕捉できます。

 また、コード中のコメントに書いている通り、StandardErrorを捕捉する場合は、「rescue => e」というように省略して書くことができます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。