Ruby throwの使いどころ

例外ではなく、制御構造の話。

Effective Ruby

Effective Ruby

項目27 スコープから飛び出したい時にはraiseではなくthrowを使おう

catchとthrowはgotoの安全バージョン。

StopIterationを使った例

begin
  @characters.each do |character|
    @colors.each do |color|
      if player.valid?(character, color)
        raise(StopIteration)
      end
    end
  end
rescue StopIteration
  # ...
end

catchを使うと

よりsimpleに書ける。第二引数に値を渡せるのも良いね。これ知らなかったな。

match = catch(:jump) do
  @characters.each do |character|
    @colors.each do |color|
      if player.valid?(character, color)
        throw(:jump, [charcter, color])
      end
    end
  end
end

覚えておくべき事項

  • 複雑な制御フローが必要な時には、raiseではなく、throwを使うようにしよう。throwを使うと、ボーナスとしてスタックの上にオブジェクトを送ることができる。catchの戻り値はそのオブジェクトだ。
  • できる限り単純な制御構造を使おう。catchとthrowの組み合わせは、単純にreturnでスコープから抜け出すメソッドとそれに対する呼び出しで置き換えられることが多い。

Ruby リソースはブロックとensureで管理しよう

File::openの話にとどまらず、リソースを確保するというコードの抽象化の紹介。
(File::openについてはここでは省略)

Effective Ruby

Effective Ruby

項目24 リソースはブロックとensureで管理しよう

ensureを使ってリソースの解放を行う例

class Lock
  def self.acquire
    lock = new
    lock.exclusive_lock!
    yield(lock)
  ensure
    lock.unlo0ck if lock
  end
end

Lock.qcquire do |lock|
  # ここで例外が発生しても問題なし
end

しかしこれだとblockがない場合が考慮されていない。

改良版

class Lock
  def self.acquire
    lock = new
    lock.exclusive_lock!

    if block_given?
      yield(lock)
    else
      lock # Lock::newのように動作する
    end
  ensure
    if block_given?
      lock.unlo0ck if lock
    end
  end
end

本書の例だと、少しblock_given?の所が見辛いですが、まぁ簡単に実装できます。

覚えておくべき事項

  • 確保したリソースを解放するためにensure説を書こう。
  • リソース管理を抽象化するために、クラスメソッドでblockとensureパターンを使おう。
  • ensure節で変数を使うときには、その前に変数が初期化さレテいるかどうかを確かめよう。

Rubyの例外Tips

みんな最初は迷う例外のTips。

Effective Ruby

Effective Ruby

項目22 raiseにはただの文字列ではなくカスタム例外を渡そう

raiseに文字列を渡すとRuntimeErrorとなる

>> raise("coffee machine low on water")
RuntimeError (coffee machine low on water)

カスタムエラークラスの名称

これは大抵どんな本でも記載があるのでみんな知っていると思うが、
rubyのExceptionは低水準エラーを含むので、通常の例外はStandardErrorを継承するのが一般的。
名称も〜Errorにしておこうという話。
名前は〜Exceptionとしてしまっていたこともあるので、今後はErrorを使うようにしよう。
 

カスタム例外クラスの構成

メッセージ以外に補助情報を持たせたいときカスタム例外を使うと便利。
本書では3Dプリンタを操作するユーティリティを例にし、温度を格納するようにしている。

class TemperatureError < StandardError
    attr_reader :temperature

    def initialize(temperature)
      @temperature = temperature
      super("invalid temperature: #{@temperature}")
    end
end

raise(TemperatureError.new(180))
TemperatureError (invalid temperature: 180)

1プロジェクトに複数あるなら独自のクラス階層にまとめることを検討すべき。

覚えておくべき事項

  • 例外としてraiseに文字列を渡すのは避けよう。この場合汎用のRuntimeErrorオブジェクトが使われる。そうではなく、カスタム例外クラスを作ろう。
  • カスtクァむ例外クラスはStandardErrorを継承し、クラス名がErrorで終わるようにしよう。
  • 一つのプロジェクトのために複数の例外クラスを作るときには、まずStandardErrorを継承する基底クラスを作り、ほかの例外クラスはそのカスタム基底クラスを継承するように構成しよう。
  • カスタム来街クラスのためにinitializeメソッドを書くときには、superを呼び出すようにしよう。super呼び出しにエラーメッセージを渡せばなおよい。
  • initializeでエラーメッセージを設定するときには、raiseでエラーメッセージを設定すると、initializeのメッセージが上書きされてしまうことに注意しよう。