Help us understand the problem. What is going on with this article?

【Ruby】例外処理

何かを学ぼうとする時、ただ本を読み進めても、実際にそれを必要とするシーンに局面しないと実感は湧かないということで、軽く読み流す程度でいいと言われたことがあります。自分は人の書いたコードを何となく理解する程度しかできません。そんな時、これが分かる人は楽しいだろうなぁと思うのです。なので、そう言われてもちゃんと理解したい性格ゆえに、こうしてまとめています。

この例外やエラーについては少し難しく感じました。記事の一番最後に書きましたが、特に公式ドキュメントにあった raise のサンプル文法では Exceptionerror_type の違いを理解するのは時間がかかりました。 制御構造 raise 頭の中では何とか支えて立っているものが崩れてしまいそうな状態です。

間違いなどありましたらコメントをお願いします。

begin ~ rescue ~ end

プログラムの処理が期待通りにいかない事はよくあります。Ruby には、エラーが起こった場合に臨時で処理される例外処理といわれるものが用意されています。例外処理という考え方が存在しない言語もあるようですが、その場合は falsenill を返される度にそれに応じた処理を書かなければならないらしいです。Ruby で例外処理は以下のように書きます。

begin
# 例外が起こるかも知れないコード
rescue => error # 変数(例外オブジェクトの代入)
# 例外が発生した時のコード
end

わざと例外を発生させたコードの例を書きます。

# 実行してもエラーになるメソッドを定義
def hello
  File.open( "/hello/file" )
end

begin
  # エラーを起こす可能性のあるコード
  hello
# 例外オブジェクトを変数 error に代入
rescue => error
  # 変数の値を表示
  puts error
end

特殊変数

例外オブジェクトを代入する変数を指定しなかった時の値は $!$@ に代入されます。 rescue で例外オブジェクトを代入する変数は、指定しようがしなかろうが値は代入されます。これらは Ruby に用意された特別な意味を持つ変数で、 特殊変数 と呼ばれます。この二つは代入されるものが異なります。

特殊変数 説明
$! 例外が起こった時にその例外オブジェクト(Exception)が代入されます。もしも複数の例外があった場合は一番最後の例外オブジェクトが代入されることになります。例外が発生しなかった場合にアクセスすると nil が得られます。
$@ 例外が起こったバックトレースを配列にして代入してくれます。バックトレースとは、例外を発生させたプログラム全体の流れを追って、原因となったコードの情報を示すものです。
# 実行してもエラーになるメソッドを定義
def hello
  File.open( "/hello/file" )
end

begin
  # エラーを起こす可能性のあるコード
  hello
rescue
  # ローカル変数ではなく特殊変数を使った例
  puts $!
  puts $@
end

ここでは特殊変数を二つしか紹介していませんが、他にもたくさんありますので、知りたい方はリンク先へどうぞ。
- module Kernel -特殊変数-

例外オブジェクトに関するメソッド

メソッド 説明
Object#class レシーバがどのクラスのインスタンスなのかを教えてくれる。例外オブジェクトをレシーバにすると Exception であることが分かる。
Kernel.#raise 例外を発生させるモジュール関数。 (Kernel は、クラス Object にインクルードされているので、全てのクラスから使用が可能です。)
Exception#message エラーメッセージを返す。
Exception#backtrace 例外の原因となった箇所の情報を Array オブジェクトの String 要素にして返します。 $@p $!.backtrace と同じ意味になります。
begin
  # わざと例外を起こす
  raise
# 例外オブジェクトを変数 error に代入
rescue => error
  puts error
begin
  # わざと例外を起こす
  5 + nil
# 例外オブジェクトを変数 error に代入
rescue => error
  # エラーを表すメッセージを表示
  puts error.message
end
begin
  # わざと例外を起こす
  raise
# 例外オブジェクトを変数 error に代入
rescue => error
  # 変数 error を配列で表示
  p error.backtrace
end

begin ~ rescue ~ ensure ~ end

エラーが発生しようがしなかろうが、必ず実行したい処理がある時には、 ensure を使います。

begin
  # 例外が起こるかも知れないコード
rescue => # 変数(例外オブジェクトの代入)
  # 例外が起こった時のコード
ensure
  # 例外が起こっても起こってなくても実行したいコード
end
# メソッド定義 引数にコピー元とコピー先を指定
def copy( from, to )
  # 指定されたコピー元ファイルを開いて代入
  src = File.open( from )
  begin
    # 指定されたコピー先ファイルを書き込みモードで開いて代入
    dst = File.open( to, "w" )
    # コピー元を全て読み込んで代入
    data = src.read
    # 読み込んだコピー元をコピー先に代入
    dst.write( data )
    # コピー先ファイルを閉じる。
    dst.close
  ensure
    # 例外が起こっても起こらなくても実行したい時のコード
    # コピー元ファイルをとにかく閉じる
    src.close
  end
end

制御構造 retry

rescue の中で retry を使うと、begin の処理が成功するまでやり直します。しかし、永久に成功しない処理を繰り返すと無限ループにはまりますので注意です。

# 入力されたファイルを代入
file = ARGV[0]
begin
  # 代入されたファイルを開いて代入
  io = File.open( file )
rescue
  # 開けなかったのなら実行
  # 10 秒間待機する
  sleep( 10 )
  # begin からやり直す
  retry
end

# 読み込んで代入
data = io.read
# 入力されたファイルを閉じる
io.close

rescue 修飾子

if や unless のように rescue も一行で書けます。

エラーを起こすかも知れない処理 rescue エラーを起こした時に実行する処理

例えば以下のコードでは、モジュール関数 Integer がエラーを起こした時、代わりに = の右側を全体の式と見なして 0 を代入します。

default_num = Integer(val) rescue 0

補足

メソッドを定義する時、そのメソッドが実行しようとする処理の全体を begin ~ end を記述せずに rescueensure だけを書くこともできます。

def
# メソッドの処理
rescue
# 例外処理
ensure
# 後処理
end

この書き方をクラス定義の中でも使用できるのですが、クラス定義の中で例外が起こると、例外が発生したそれ以降のメソッドの定義が行われなくなるので普通は使われないそうです。

複数の例外

例外がいくつかに分かれる時、それぞれに対する rescue の処理を分けて書くこともできます。

begin
  # 例外が起こるかも知れないコード
rescue Exception1, Exception2 => 変数
  # Exception1 または Exception2 に対する例外処理
rescue Exception3 => 変数
  # Exception3 に対する処理
rescue
  # 上記以外の例外に対する処理
end

クラスを指定して、想定される例外に対処してみます。

file1 = ARGV[0]
file2 = ARGV[1]

begin
  # 例外を起こすかも知れないコード
  io = File.open( file1 )
rescue Errno::ENOENT, Errno::EACCES
  # Errno::ENOENT または Errno::EACCES に対する例外処理
  io = File.open( file2 )
end

Errno::ENOENT はファイルが存在しなかったことを示す例外クラスで、Errno::EACCES はファイルを開く権限がなかったことを示す例外クラスです。ここで例外クラスを何も指定しなかった場合、その処理は StandardError という例外クラスに対して対処されるそうです。 Exception の継承関係についてはリンクを参照してください。
例外クラス

制御構造 raise

上記でも使った raise ですが、このメソッドを使ってエラーをどのように起こすかをコントロールできます。自分が求める条件下でエラーを発生させたり、直前に対処したエラーを再度発生させたり、例外を呼び出し元に伝える為に使ったりします。

エラータイプ 説明
raise message RuntimeError を発生させ、メッセージを文字列として例外オブジェクトに格納します。
raise error_type 指定されたエラーが起こります。
raise Exception, message 上のふたつを行います。指定されたエラーが起こされ、メッセージを文字列として例外オブジェクトに格納します。
raise rescue の外では RuntimeError を発生させます。 rescue の中では、最後に起こったエラー $1 を再度起こさせます。
# RuntimeError を発生させて
# 例外オブジェクトにメッセージを格納。
raise "Invalid Error"

# SyntaxError を発生させる。
raise SyntaxError

# 引数に文字列オブジェクトを指定すると
# それをメッセージとする RuntimeError を発生させる。
raise SyntaxError.new( "Invalid Error" )

# 引数に例外オブジェクトを指定すると
# そのエラーを発生させる。
raise Exception.new( SyntaxError )

# rescue の外では RuntimeError、
# rescue の中では最後のエラーをもう一度発生。
raise
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした