何かを学ぼうとする時、ただ本を読み進めても、実際にそれを必要とするシーンに局面しないと実感は湧かないということで、軽く読み流す程度でいいと言われたことがあります。自分は人の書いたコードを何となく理解する程度しかできません。そんな時、これが分かる人は楽しいだろうなぁと思うのです。なので、そう言われてもちゃんと理解したい性格ゆえに、こうしてまとめています。
この例外やエラーについては少し難しく感じました。記事の一番最後に書きましたが、特に公式ドキュメントにあった raise
のサンプル文法では Exception
と error_type
の違いを理解するのは時間がかかりました。 制御構造 raise 頭の中では何とか支えて立っているものが崩れてしまいそうな状態です。
間違いなどありましたらコメントをお願いします。
begin ~ rescue ~ end
プログラムの処理が期待通りにいかない事はよくあります。Ruby には、エラーが起こった場合に臨時で処理される例外処理といわれるものが用意されています。例外処理という考え方が存在しない言語もあるようですが、その場合は false
や nill
を返される度にそれに応じた処理を書かなければならないらしいです。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
を記述せずに rescue
と ensure
だけを書くこともできます。
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
Comments