Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyで例外をrescue(捕捉)する方法を徹底解説

アプリケーション内で発生した例外は、そのまま放置するとコールスタックの最上部まで伝播し、アプリケーション全体をクラッシュさせてしまいます。これを防ぐために、Rubyではrescueキーワードを使って例外を捕捉(キャッチ)します。

さらに、Rubyで例外を捕捉する際には、どの例外クラスを対象とするかを具体的に指定することも可能です。

begin
  raise 'この例外は捕捉されます!'
rescue StandardError => e
  puts "Rescued: #{e.inspect}"
end

補足raiseを呼び出す際に例外クラスを指定しなかった場合、RubyはデフォルトでRuntimeErrorを発生させます。

複数の例外クラスをまとめて捕捉する

rescueには、単一の例外クラスだけでなく、例外クラスを複数渡すこともできます。これにより、複数種類のエラーに対して同じ処理で応答できるようになります。

begin
  raise 'この例外は捕捉されます!'
rescue StandardError, AnotherError => e
  puts "Rescued: #{e.inspect}"
end

複数のrescueブロックでエラーを使い分ける

逆に、rescueブロックを複数並べることで、エラーの種類ごとに異なる処理を行うことも可能です。ライブラリがシナリオごとに異なる例外を発生させるような場合に特に有用です。

begin
  raise 'この例外は捕捉されます!'
rescue StandardError => e
  puts "Rescued: #{e.inspect}"
rescue AnotherError => e
  puts "別のブロックで捕捉: #{e.inspect}"
end

Rubyの例外階層

Rubyの例外階層は、さまざまな種類のエラーを区別すると同時に、すべての例外クラスを個別に列を個別に列挙しなくても、あるグループに属するエラーをまとめて捕捉できるよう設計されています。

ライブラリが独自の例外サブクラスを定義することもありますが、Ruby 2.5に組み込まれている例外クラスの一覧は以下のとおりです。

- NoMemoryError
- ScriptError
    - LoadError
    - NotImplementedError
    - SyntaxError
- SecurityError
- SignalException
    - Interrupt
- StandardError(`rescue` のデフォルト)
    - ArgumentError
        - UncaughtThrowError
    - EncodingError
    - FiberError
    - IOError
        - EOFError
    - IndexError
        - KeyError
        - StopIteration
    - LocalJumpError
    - NameError
        - NoMethodError
    - RangeError
        - FloatDomainError
    - RegexpError
    - RuntimeError(`raise` のデフォルト)
    - SystemCallError
        - Errno::*
    - ThreadError
    - TypeError
    - ZeroDivisionError
- SystemExit
- SystemStackError
- fatal(捕捉不可能)

rescueブロックで例外クラスを省略した場合、暗黙的にStandardErrorが指定されたものとみなされます。ArgumentErrorNoMethodErrorはいずれもStandardErrorのサブクラスであるため、これらがブロック内で発生しても自動的に捕捉されます。

SystemCallErrorの活用例

例外階層の仕組みを理解するうえで好例なのがSystemCallErrorです。これはプラットフォーム依存の低レベルな例外クラスで、ファイルの読み書き時に遭遇することが多いクラスです。

RubyのFile.readメソッドは、ファイルの読み込みに失敗すると例外を発生させます。失敗の原因としては、ファイルが存在しない、プログラムに適切な読み取り権限がないなど、いくつか考えられます。

こうした問題はOSに依存するため、実行環境のオペレーティングシステムによって、Rubyが発生させる例外の種類も変わります。このような低レベルのエラーに対して、Rubyはプラットフォームごとに異なるErrno::*系の例外を実装しています。

これらのErrno::*例外はすべてSystemCallErrorのサブクラスです。プラットフォーム固有の例外ではありますが、SystemCallErrorを指定して捕捉すれば、どの環境でも同じコードで対応できます。

begin
  File.read("does/not/exist")
rescue SystemCallError => e
  puts "Rescued: #{e.inspect}"
end

例外の握りつぶしに注意する

例外を捕捉する際は、意図せず例外を「握りつぶして」しまうことを防ぐため、できる限り具体的な例外クラスを指定するのがベストプラクティスです。

image = nil
 
begin
  File.read(image.filename)
rescue
  puts "ファイルを読み込めません!"
end

この例では、変数imagenilであるため、#filenameを呼び出した時点でNoMethodErrorNoMethodError: undefined method `filename' for nil:NilClass)が発生します。しかし、StandardErrorの全サブクラス(NoMethodErrorを含む)が捕捉対象となるため、この例外は握りつぶされ、「ファイルを読み込めません!」というメッセージだけが表示されてしまいます。本来なら検出できたはずのコード上のバグが、このように隠れてしまうのです。

補足:技術的には可能ですが、rescueブロックでExceptionスーパークラスを指定することは強く非推奨です。

まとめ

Rubyでの例外の発生(raise)や捕捉(rescue)について、まだ疑問点がある方は、ぜひ@AppSignalまでお気軽にお問い合わせください。もちろん、本記事が役立ったかどうかのご感想や、今後取り上げてほしいテーマのご提案もお待ちしています。

  1. Rubyで例外にコンテキストデータを追加する3つの方法

    標準のバックトレースやエラーメッセージだけでは、エラーの原因を特定するのに十分な情報が得られないことがあります。そんなときに役立つのが、例外への追加データの付与です。幸い、Rubyではこれをとても簡単に実現できます。 エラーメッセージをカスタマイズする 例外にコンテキスト情報を加える最もシンプルな方法は、メッセージそのものに情報を組み込んでしまうことです。次の例では、発生した例外を捕捉し、新しいメッセージを付けて再送出しています。 begin raise foo rescue => e raise e.class, bar end # RuntimeError: bar

  2. RubyのTracePointで複雑な例外の挙動を調査する方法

    例外の挙動を把握するのは、時に非常に難しいものです。特に大規模なアプリケーションではその傾向が強まります。既存のプロジェクトでコードを書いていて、例外をraiseしたのに奇妙なことが起こった経験はありませんか?例外がどこかで握りつぶされている。環境変数が書き換えられている。あるいは、自分のraiseした例外が別の例外に包まれてしまう——そんなケースです。 この記事では、TracePointを使ってアプリケーション内の例外についてより詳しい情報を得るためのシンプルな方法を紹介します。例外が握りつぶされていたとしても、その動きを追跡できます。 わかりやすい例:コントローラでrescueできない