[Ruby入門] 13. 例外処理

&gt;&gt; 連載の目次は [こちら！](http://qiita.com/prgseek/items/237d02be844534063f1d)

### ■ Rubyの例外クラスの構成

<pre>
Exception
    |
    |- StandardError
    |   |- TypeError とか RuntimeError とかの、アプリケーション側に起因する例外たち
    |   |- 僕らがアプリケーション向けに独自に作成する例外たち
    |
    |- その他の例外: メモリのエラーみたいな、システム側の例外。アプリケーション側では救えない類。

</pre>

+ 例外の基底クラスは Exception クラス
+ その配下は、大きく StandardErrorクラス系と、それ以外に分かれる
+ StandardErrorクラス系は、いわゆるアプリケーション側の例外
+ それ以外はシステム側のエラーで、通常はアプリケーションでは復帰できない例外たち（メモリのエラーとか）
+ 通常の例外クラスは、XxxError という命名が正しいのかな。XxxExceptionじゃなくて。

~~・rescueでキャッチできるのは StandardError系だけ~~
↑間違いです！ 型を明示してrescueすれば、それ以外の例外もちゃんと拾えます。
コメント欄の @scivola さんの記述も参照してください。


### ■ begin〜rescue〜end （いわゆる try〜catch 的な）

#### ● 一番シンプルに試してみる
```ruby
begin
  raise "oh my god !!"  # (1)
rescue => e             # (2)
  p e   # (3)  #<RuntimeError: oh my god !!>
  p $!  # (4)  #<RuntimeError: oh my god !!>
end
```

(1) raise で例外を投げる（throw的な）。例外クラスを指定しない場合、RuntimeError が投げられる
(2) rescue で catch する。例外の型を指定しない場合、StandardError系の全ての例外をキャッチする
(3) rescue が受け取った e が、例外オブジェクト。
(4) グローバル変数「$!」にも、例外オブジェクトがセットされている


#### ● 型を指定してrescueしてみる
```ruby
def exception_test(num)

    begin

        case num
        when 1
            # raiseする
            raise ArgumentError.new, "bad argument"
        when 2
            # 0で割っちゃう
            5 / 0
        when 3
            # 無いメソッド呼んじゃう
            String.not_exist_method
        else
            # もうどうしようもないやつ
            raise RuntimeError, "oh my god!"
        end

    # 例外の型を明示してresucue
    rescue ArgumentError => e
        print "rescue 1: "; p e

    # 例外の型は複数指定してresucueできる
    rescue ZeroDivisionError, NoMethodError => e
        print "rescue 2: "; p e

    # 例外の型を指定しなければ、StandardError系すべてresucueできる
    # 以下は「rescue StandardError => e」って書くのと同じこと。
    rescue => e
        print "rescue 3: "; p e
    end

end

exception_test(1)
   # rescue 1: #<ArgumentError: bad argument>

exception_test(2)
   # rescue 2: #<ZeroDivisionError: divided by 0>

exception_test(3)
   # rescue 2: #<NoMethodError: undefined method `not_exist_method' for String:Class>

exception_test(4)
   # rescue 3: #<RuntimeError: oh my god!>
```


### ■ 独自の例外クラスを定義する
```ruby
# StandardError を継承する。もちろんここからさらにサブクラス階層化しても良し。
class OverWorkError < StandardError
  attr_reader :hour
 
  # 例外のnewで引数を受け取ってインスタンス変数で保持してみる
  def initialize(hour)
    @hour = hour
  end
end

begin
  (1..10).each do |i|
      raise OverWorkError.new(i), "働きすぎ！" if (i > 8)
      print i.to_s + ".."
  end

rescue OverWorkError => e
  # 例外オブジェクトからインスタンス変数を取得。messageメソッドでメッセージを取得
  puts e.hour.to_s + "時間なんて" + e.message

end

# 実行結果
# 1..2..3..4..5..6..7..8..9時間なんて働きすぎ！
```


### ■ 例外が発生しなかった場合になんかする（else）
```ruby
begin
  print "予想外のことが全く起きないって..."
rescue => e
  p e
else
  puts "けっこう退屈かもねー"
end
```


### ■ 例外が発生しようがしまいがなんかする（ensure）
```ruby
begin
  print "世は全てこともなし..."
rescue => e
  p e
ensure
  puts "で、でもなんか言わせてー！"
end
```


### ■ 例外発生時にリトライする（retry）
```ruby
i = 0
begin
  i += 1
  raise OverWorkError.new(i), "働きすぎ！"
rescue => e
  # 無限ループに注意！なんか条件付きでretryすること。
  retry if (i <= 8)
  puts e.hour.to_s + "時間なんて" + e.message  # 9時間なんて働きすぎ！
end

# 実行結果
# 9時間なんて働きすぎ！
```

### ■ 簡易なrescue（後置rescue）
```ruby
  raise OverWorkError.new(i), "働きすぎ！" rescue puts "おうちに帰ろう！"
  # この場合、StandardError系をまとめてrescueする
```