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

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

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

例外クラスを作ってみる

 前節で組み込みの例外クラスを紹介しましたが、必要ならば、自分で例外クラスを作成することもできます。exception01.rbに、例外クラスを作成する例を示します。

class UnacceptableRequidError < StandardError
  attr_reader :obj
 
  def initialize(obj)
    @obj = obj
  end
end
 
class Vessel
  def pour_out
    requid = @requid
    @requid = nil
    requid
  end
 
  def pour_in(requid)
    if requid.to_s == acceptance
      @requid = requid
    else
      raise UnacceptableRequidError.new(requid), "unacceptable"
    end
  end
 
  def acceptance
    raise NotImplementedError.new, "#acceptance is not implemented!"
  end
end
 
class Teapot < Vessel
  def initialize(requid)
    pour_in(requid)
  end
 
  def acceptance
    "tea"
  end
end
 
class Decanter < Vessel
  def initialize(requid)
    pour_in(requid)
  end
 
  def acceptance
    "wine"
  end
end
 
class Kettle < Vessel
  def initialize(requid)
    pour_in(requid)
  end
end
exeption01.rb

 このコードは、容器を表すクラス「Vessel」と、Vesselを継承した「Teapot」(お茶を入れる容器)と「Decanter」(ワインなどを入れる容器)といったクラスを定義し、それを利用する例となっています。クラスについて復習の意味も兼ねて、少し複雑な例にしてみました。

 Teapotにはお茶しか入れることはできませんし、Decanterにはワインしか入れることができません(できないと思ってください!)。なので、クラスの利用者が許容できない液体を入れようとした場合は、例外を発生させて利用者に知らせるものとしましょう。

利用例

 では、exception01.rbと同じディレクトリでpryを起動して、定義した各クラスを使ってみましょう。

[1] pry(main)> require_relative "exception01"
=> true
[2] pry(main)> teapot = Teapot.new("tea")
=> #<Teapot:0x007faa8dacc008 @requid="tea">
[3] pry(main)> teapot.pour_out
=> "tea"
[4] pry(main)> teapot.pour_out
=> nil
[5] pry(main)> teapot.pour_in("coffee")
UnacceptableRequidError: unacceptable
from /Users/flost/work/ruby_rails_tutorial/ruby_21_guide/09/exception01.rb:20:in `pour_in'
[6] pry(main)> decanter = Decanter.new("wine")
=> #<Decanter:0x007faa8d99fce8 @requid="wine">
[7] pry(main)> decanter.pour_in("beer")
UnacceptableRequidError: unacceptable
from /Users/flost/work/ruby_rails_tutorial/ruby_21_guide/09/exception01.rb:20:in `pour_in'

 [1]では、require_relativeを使って、exception01.rbを読み込んでいます。この1行を実行することで、exception01.rbの中で定義されたクラスを使うことができます。

 [2]では、Teapotクラスのオブジェクトを作成し、初期値として「tea」を与えています。Teapotは「tea」を許容するので、無事オブジェクトが生成され、[3]でteapotに格納されている「tea」を取り出せます。また、一度取り出されると中身がnilになるので、[4]では返り値がnilとなっています。[5]では「coffee」をteapotに格納しようとしていますが、許容できないのでUnacceptableRequidErrorが発生しています。

 [6]から[7]は、Decanterの利用例です。[6]では「wine」を初期値としてDecanterオブジェクトを生成できていますが、「beer」は許容できないので、[7]でUnacceptableRequidErrorが発生しています。

コードの解説

 exeption01.rbは1〜7行目で、UnacceptableRequidErrorという例外を定義しています。4行目から6行目のコンストラクタで、UnacceptableRequidErrorのオブジェクト変数に、クラスの利用者が入れようとしたオブジェクトを格納しておきます(格納したオブジェクトの利用例は、次節の例外の捕捉で説明します)。

 9〜23行目はVesselクラスの定義です。Vesselクラスは三つのメソッドpour_outとpour_inとacceptanceを持っており、pour_outは格納された液体を外に注ぎ、pour_inは外から液体を注ぐためのメソッドです。また、acceptanceは許容する液体を示す文字列を返すためのメソッドです。

 pour_outはインスタンス変数@requidに格納された液体をローカル変数requidにいったん移し、@requidにnilを代入し、一時的に移しておいたrequidの内容を返します。この一連の動作によって、「容器の中身を外に移して空(nil)になった」ということを表しています。

 pour_inは引数に与えられたオブジェクトを、acceptanceメソッドの返すオブジェクトと付き合わせて、同じならインスタンス変数@requidに格納し、違えばUnacceptableRequidError例外を発生させます。TeapotやDecanterといった派生クラスでacceptanceをオーバーライドすることによって、派生クラスの実装次第で許容する液体を変えることができます。

 ここで、Vesselクラスでのacceptanceの定義に注目してください。25行目でNotImplementedErrorという例外を発生させています。これは、Rubyのクラス設計でよく使われるテクニックです。

 もし派生クラスでacceptanceメソッドがオーバーライドされていなければ、基底クラスVesselのacceptanceメソッドが呼ばれます。このとき、NotImplementedError、つまり「メソッドが実装されていませんよ」という例外が発生するので、クラスの利用者にacceptanceメソッドを実装するように促せます。

 サンプルコードではKettleクラスがacceptanceメソッドをオーバーライドしていないので、NotImplementedErrorが発生します。

[8] pry(main)> Kettle.new("water")
NotImplementedError: #acceptance is not implemented!
from /Users/flost/work/ruby_rails_tutorial/ruby_21_guide/09/exception01.rb:25:in `acceptance'

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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