Rubyの例外処理についてまとめ

Ruby技術者認定試験(シルバー)の試験を受けるまえに例外処理についてまとめました。

出題としては数問ですが、理解すればこのあたりは比較的取りやすいと思います。
ただあまり使用したことがないので、わたしは最初はとっつきづらかったです。

まず処理の部分を学ぶ前にどんな例外があるのか整理します。

##例外の種類
> 
- Syntax Error :文法エラーがあったとき。たとえばシングルコーテションが抜けてるとか
- SignalException : 捕捉してないシグナルをうけた場合
- ArgumentError : 引数の数が合わない場合や値が正しくない場合。

```ruby

def arg(a,b)
  p a
  p b
end
arg('hello','world')  #引数が正しい
"hello"
"world"
arg('hello')          #引数が足りない
ArgumentError: wrong number of arguments (given 1, expected 2)
```
>
- RuntimeError : 特定の例外クラスには該当しないエラーが発生した場合や例外クラスを省略したraiseの呼び出し　

```ruby
raise  
RuntimeError: 
```

- NameError : 未定義のローカル変数や定数を参照した場合

```ruby
TEISU = 1
puts TEI   #未定義の定数TEIを呼び出す
NameError: uninitialized constant TEI
```
>
- NoMethodError : 未定義のメソッドを呼び出した場合

```ruby
self.bar   #未定義のクラスメソッド
NoMethodError: undefined method `bar' for main:Object
```
>
- ZeroDivisionError : 整数にたいし整数の0で除算を行った場合

```ruby
puts 3/0
ZeroDivisionError: divided by 0
```

以上のような例外があることが分かりました。

続いては例外処理について

##例外処理
例外処理にはbegin節、rescue節、else節、ensure節があります。
まずbegin節を使い、例外が発生する可能性がある部分をbeginとendで囲みます。
例外が出ている場合はrescue節で例外処理を実行します。

```ruby
begin
  p 例外を発火　#NameError
  p "ここでは例外処理は実行されません"
rescue
  p "ここで例外処理が実行されます!"
end
#実行結果
"ここで例外処理が実行されます!"
```
逆に例外が出てない場合は

```ruby
begin
  p "例外を発火しません"
  p "ここでは例外処理は実行されません。正常なものが実行されます。"
rescue
  p "正常なので実行されません"
end
#実行結果
"例外を発火しません"
"ここでは例外処理は実行されません。正常なものが実行されます。"
```
また例外が発生されない場合はelse節を指定することで例外が発生しなかったときの処理を記述できます。

```ruby
begin
  p "例外を発火しません"
rescue
  p "ここで例外処理が実行されますが例外がない場合は実行されません"
else
  p " 例外がないので、else節で正常な処理が実行されます。"
end
#実行結果
"例外を発火しません"
" 例外がないので、else節で正常な処理が実行されます。"
```
また例外処理には例外が発生したかどうかに問わず必ず実行されるensure節があります。

```ruby
begin
  p 例外発生
rescue
  p '例外を検知したので、rescue節で捕捉しました'
else
  p '例外が出ているので、else節は実行されません'
ensure
  p '例外が出ようが出まいがensure節は実行します'
end
#実行結果
"例外を検知したので、rescue節で捕捉しました"
"例外が出ようが出まいがensure節は実行します"
```
正常な場合はどうか

```ruby
begin
  p '正常です'
rescue
  p '例外を検知されないので、rescue節は実行されません'
else
  p '正常なので、else節は実行されます'
ensure
  p '例外が出ようが出まいがensure節は実行します'
end
#実行結果
"正常です"
"正常なので、else節は実行されます"
"例外が出ようが出まいがensure節は実行します
```

またrescue節は単独で使用ができる

```ruby
#例外がある場合
a rescue 'aaa' #NameError 　未定義の a　を呼び出す
"aaa"

#正常な場合
'a' rescue 'aaa' #正常な文字列aを呼び出す
"a"
```

またメソッド内部でもrescue節で捕捉できる

```ruby
def abc
  p a #未定義のaを呼び出す
rescue
  p '例外を捕捉しました。'
end
abc #メソッドabcを実行
"例外を捕捉しました。"
```
当然ですがrecue節の後ろで例外が出ている場合は例外を捕捉できません。


```ruby
def xyz
  p '正常です'
rescue
  p 'rescue節の前は正常です'
  p e #未定義のeを呼び出す
end
xyz #メソッドxyzを実行
"正常です"
```

また例外は発生した順番に処理されます。

```ruby
begin
  puts a # NameError
  def arg(b,c)
    puts 'hello'
  end
  arg(b) #ArgumentError
  method('hello') # NomethodError
rescue => e     # 出ている例外を変数「e」に代入。
  puts e.class      # 例外クラスを出力
  puts '########'
  puts e.message    # 例外メッセージを出力
end
#実行結果
NameError
########
undefined local variable or method `a' for main:Object
````

以上が例外処理についてでした。

##raiseについて
最後にraiseの使い方についてまとめます
raiseメソッドは特に例外が出ていなくても例外をあえて出す時に使います。
使い方としては例えば例外クラスでは該当しないがエラーを出したい時に使われます。
例えばユーザーにエラーということを示したいケースなどがあります。

```ruby
begin
  puts 'aaa' #正常な処理なのでエラーは出ない
  raise RuntimeError,'Runtimeエラーを出したい'
rescue => e
  puts e.class
  puts e.message
end
#実行結果
aaa
RuntimeError
Runtimeエラーを出したい
```
またraiseメソッドと合わせてretryメソッドを使ってみようと思います。
retryメソッドは例外が出ている場合はbegin節に戻って、もう一度処理を実行します。
正常な処理が実行されるとretryメソッドから抜けることができます。

```ruby
a = 0
begin 
  b = 3/a 
  b == 1 ? (puts 'お!!') : (raise RuntimeError,'bは1ではありません')

rescue => e
  puts "bの中身:#{b}" 
 
  a += 1 
 
  puts e.class
  puts e.message
retry
else
  puts "bの中身:#{b}" 
  puts 'bが1になりました'
end
#実行結果
bの中身:             
ZeroDivisionError  
divided by 0        
bの中身:3            
RuntimeError
bは1ではありません
お!!
bの中身:1
bが1になりました
```

以上が例外処理についてでした。

今回は参考図書に[Ruby技術者認定試験合格教本（Silver/Gold対応）Ruby公式資格教科書](https://gihyo.jp/book/2017/978-4-7741-9194-2)を使用しました。