Ruby 例外処理について

# 例外処理とは
例外処理を行うことで、エラー原因やエラー箇所の特定や、発生したエラーに対する適切な処理を行うことができます。

本記事では、以下について紹介していきます。

- rescue
- ensure
- 例外処理の戻り値
- return
- rescue修飾子
- 特殊変数 `$!` `$@`
- メソッド全体に例外処理を設ける(begin/endの省略)
- raise
- カスタム例外を作成

## rescue
以下のようにbeginの下に、通常実行したいプログラムを記述します。
そのプログラムの実行中に例外が発生した場合に、呼び出したいコードをrescueの下に記述します。

```ruby
begin
  p "実行するコード"
rescue
  p "例外が発生した場合に実行されるコード"
end
```

`結果`

```
"実行するコード"
```

### else
rescueに、elseを付け加えることによって
例外が発生しなかった場合に呼び出したいコードを追加できます。

```ruby
begin
  p "実行するコード"
rescue
  p "例外が発生した場合に実行されるコード"
else
  p "例外が発生しなかった場合に実行されるコード"
end
```

`結果`

```
"実行するコード"
"例外が発生しなかった場合に実行されるコード"
```

`rescueの使用例`

例外が発生したら、「エラーが発生しました！」というメッセージを表示するブログラムを作成します。

文字列を""(ダブルクォーテーション)で囲っていないので例外となり、rescueの下にあるコードが実行されます。

```ruby
begin
  p 文字列
rescue
  p "例外が発生しました"
end
```

`結果`

```
"例外が発生しました"
```

## ensure

例外処理にensureを使用することで、例外の有無に関わらず実行される処理を記述することができます。

```ruby
begin
  p "実行するコード"
rescue
  p "例外が発生した場合に実行されるコード"
else
  p "例外が発生しなかった場合に実行されるコード"
ensure
  p "例外の有無にかかわらず 最後に実行されるコード"
end
```

`結果`

```
"実行するコード"
"例外が発生しなかった場合に実行されるコード"
"例外の有無にかかわらず 最後に実行されるコード"
```

使用用途としては、何らかの処理を行っている時にエラーが発生した場合でも、確実に実行したい処理がある場合に使います。
(例)ファイルを開いて作業するプログラムの場合、「実行中の例外発生に関わらず、最後に必ずファイルを閉じておく」といったことができます。

## 例外処理の戻り値
- 例外発生せず正常に処理が終了した場合、beginの最後の式が戻り値となります。
- 例外が発生した場合、rescueの最後の式が戻り値となります。

(例) 処理が正常に終了した場合

```ruby
class Return
  def test(n)
    begin
      1 / n
      "Begin"
    ensure
      "Ensure"
    end
  end
end

obj = Return.new
p obj.test(1)
```

```
"Begin"
```

(例) 例外処理が発生した場合

```ruby
class Return
  def test(n)
    begin
      1 / n
      "Begin"
    rescue
      'Rescue'
    ensure
      "Ensure"
    end
  end
end

obj = Return.new
p obj.test(0)
```

```
"Rescue"
```

## return
ensure内にreturn文を記述すると、

- 例外発生が取り消され、正常終了してしまう
- 例外発生の有無に関わらず、ensureの値がメソッドの戻り値となってしまう

このことから、本来期待した処理が実行されません。

### 例外発生が取り消され、正常終了してしまう
本来は下記のようにrescueをなくし、ensureのみ記述した場合は、例外処理が発生すると異常終了となります。

```ruby
class Return
  def test(n)
    begin
      1 / n
      "Begin"
    ensure
      "Ensure"
    end
  end
end

obj = Return.new
p obj.test(1)
p obj.test(0)
```

`結果`

```
(ZeroDivisionError)
```

しかし、ensure内にreturn文を記述すると、下記のように正常終了してしまいます。


```ruby
class Return
  def test(n)
    begin
      1 / n
      "Begin"
    ensure
      "Ensure"
      return "return ensure"
    end
  end
end

obj = Return.new
p obj.test(1)
p obj.test(0)
```

`結果`

```
"return ensure"
"return ensure"
```
### 例外発生の有無に関わらず、ensureの値がメソッドの戻り値となってしまう

```ruby
class Return
  def test(n)
    begin
      1 / n
      "Begin"
    rescue
      'Rescue'
    ensure
      "Ensure"
      return "return ensure"
    end
  end
end

obj = Return.new
p obj.test(1) # 本来は"Begin"が返ってくる
p obj.test(0) # 本来は"Rescue"が返ってくる
```

`結果`

```
"return ensure"
"return ensure"
```

## rescue修飾子

rescueは修飾子のように使うこともできます。
以下のように、普通に記述すると5行は書かないといけない処理がrescue修飾子を使うと1行でスッキリと記述することができます。

```ruby
class Return
  def test
    begin
      文字列
    rescue
      "error!!!"
    end
  end
end

obj = Return.new
p obj.test
```

`rescue修飾子を使用すると1行で書けます！`

```ruby
class Return
  def test
    文字列 rescue "error!!!"
  end
end

obj = Return.new
p obj.test
```

`結果`

```
"error!!!"
```

## 特殊変数
特殊変数とは、rubyに用意された特別な意味を持つ変数のことです。
rescueで例外オブジェクトを代入する変数は、指定の有無に関わらず値が代入されます。

### 特殊変数$!
> 例外が起こった時にその例外オブジェクト(Exception)が代入されます。もしも複数の例外があった場合は一番最後の例外オブジェクトが代入されることになります。例外が発生しなかった場合にアクセスすると nil が得られます。

### 特殊変数$@
> 例外が起こったバックトレースを配列にして代入してくれます。バックトレースとは、例外を発生させたプログラム全体の流れを追って、原因となったコードの情報を示すものです。

[引用参考文献]
(https://qiita.com/tsubasakat/items/6825bcefcad26da3471b)

`使用例`

コンソール(irbやpry)で例外が発生しても、バックトレースが表示されないことにより、原因がわからない場合があります。
コンソールで例外時のバックトレースを表示するには、特殊変数$@を使用します。

```
irb(main):002:0> puts $@
```

## メソッド全体に例外処理を設ける(begin/endの省略)

メソッド全体に例外処理を設ける場合は、begin/endを省略することができます。

`begin/endを記述した例`

```ruby

def hoge
  begin
    p 1/0
  rescue
    p "エラー!!!"
  end
end
```

`begin/endを省略した例`

```ruby
def hoge
  p 1/0
rescue
  p "エラー！！！"
end
```

## raise
raiseメソッドは意図的に例外を引き起こすためのメソッドです。

下記のように記述すると、raiseメソッドにより例外が引き起こされ、rescueメソッドが実行され、「エラー！！！」と出力されます。

```ruby
begin
  raise
  rescue
    p "エラー！！！"
end
```

```
"エラー！！！"
```

このメソッドはrescue内でも使用することができます。
使用用途としては、例外発生時にプログラムを異常終了させることに加えて、例外情報をログに記録したり、メール送信したい場合に使うことができます。

```ruby
class Return
  def test(n)
    begin
      1 / n
      "Begin"
    rescue
      p "エラー！！！(log)"
      raise
    end
  end
end

obj = Return.new
obj.test(0)
```

```
"エラー！！！(log)"
Traceback (most recent call last):
        2: from ruby.rb:14:in `<main>'
        1: from ruby.rb:4:in `test'
ruby.rb:4:in `/': divided by 0 (ZeroDivisionError)
```

## カスタム例外を作成

例外クラスはRubyのクラスと同様です。
StandardErrorを継承する独自の例外クラスを作成します。

(例)

```ruby
class Error < StandardError
end
```

全てのrubyの例外オブジェクトはmessage属性を持っています。
以下のように独自の例外にデフォルトのメッセージを定義してみます。

```ruby
class Error < StandardError
  def initialize(msg="Error Message")
  super
  end
end

raise Error #=> Error Message
```

独自のデータを例外に付け加えることができます。

```ruby

class Error < StandardError
  attr_reader :attr

  def initialize(msg="default Error message", attr="value")
    @attr = attr
    super(msg)
  end
end

begin
  raise Error.new("Error message", "value")
rescue => e
  puts e.attr #=> value
end
```

これで、独自の例外を作成できました。

参考記事
https://qiita.com/k-penguin-sato/items/3d8ce4e71520da2d68c4

https://qiita.com/ngron/items/4c319ae7340b72c7e566