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

RubyのExceptionとStandardErrorの違いとは?rescueの正しい使い方を徹底解説

「Rubyで決してExceptionをrescueしてはいけない!」

こんな警告を耳にしたことがあるかもしれません。確かに良いアドバイスなのですが、詳しい人でなければ意味が分かりにくいものです。この記事では、この一文の意味を丁寧に解きほぐしていきます。

Rubyでは例外を次のようにrescueできることは、ご存知の方も多いでしょう。

begin
  do_something()
rescue => e
  puts e # eはエラー情報を含む例外オブジェクト
end

さらに、例外クラス名を指定すれば、特定のエラーだけを捕捉することもできます。

begin
  do_something()
rescue ActiveRecord::RecordNotFound => e
  puts e # RecordNotFound、およびそのサブクラスの例外のみを捕捉
end

Rubyにおけるあらゆる例外は、実は単なるクラスです。上の例でいうActiveRecord::RecordNotFoundは、一定の規約に従ったクラス名にすぎません。

ここが重要なポイントです。RecordNotFoundをrescueすると、そのクラスを継承している例外もすべて一緒に捕捉されてしまいます。

なぜExceptionをrescueしてはいけないのか

Exceptionをrescueすることの問題点は、Exceptionを継承するすべての例外を捕捉してしまうことにあります。そう……全部です!

これが問題になるのは、Rubyが内部で使用している例外が存在するからです。それらはアプリケーションとは無関係のものであり、うっかり握りつぶしてしまうと深刻な事態を引き起こします。

代表的な例をいくつか挙げます。

  • SignalException::Interrupt - これをrescueしてしまうと、Ctrl+Cでプロセスを中断できなくなります。

  • ScriptError::SyntaxError - 構文エラーを握りつぶすと、puts("Forgot something) のような書き間違いがサイレントに失敗し、気づくのが遅れます。

  • NoMemoryError - メモリを使い果たした後もプログラムが動き続けたらどうなるのか……知らなくて済むなら、それに越したことはありません。

begin
  do_something()
rescue Exception => e
  # これはNG。すべての例外が握りつぶされ、何ひとつ外に漏れません。
end

おそらくあなたは、こうしたシステムレベルの例外を捕捉したいわけではないはずです。本当に捕まえたいのは、自分のコードが引き起こしたアプリケーションレベルのエラーだけでしょう。

幸い、それを実現する方法はとてもシンプルです。

代わりにStandardErrorをrescueしよう

私たちが普段気にかけるべき例外は、すべてStandardErrorを継承しています。おなじみの仲間たちです。

  • NoMethodError - 存在しないメソッドを呼び出そうとしたときに発生

  • TypeError - 1 + "" のような型不一致の処理で発生

  • RuntimeError - 言わずと知れた定番のRuntimeError

こうしたエラーを捕捉するには、StandardErrorをrescueします。次のように明示的に書くこともできます。

begin
  do_something()
rescue StandardError => e
  # アプリ自身の例外だけが捕捉されます。SyntaxErrorなどは素通りします。
end

しかし実は、Rubyはもっと便利な仕組みを用意してくれています。

例外クラスを一切指定しなかった場合、Rubyは自動的にStandardErrorを指定したものとみなします。つまり、次のコードは上のコードと完全に同じ意味になります。

begin
  do_something()
rescue => e
  # StandardErrorをrescueするのと同じ
end

独自の例外はStandardErrorを継承すべき

では、自分でカスタム例外クラスを作る場合はどうすればよいのでしょうか?

答えは明快です。必ずStandardErrorを継承し、決してExceptionを直接継承してはいけません。Exceptionを継承してしまうと、rescueの期待される挙動が壊れてしまいます。「アプリケーションレベルのエラーはすべて捕捉しているはず」と信じていたのに、あなたの作った例外だけはrescue文を素通りしてしまうのです。

class SomethingBad < StandardError
end

raise SomethingBad

Rubyの例外ツリー(継承階層)

Rubyの例外はクラス階層として実装されているため、全体像を一度目にしておくと理解がぐっと深まります。以下は、Ruby標準ライブラリに含まれる例外クラスの一覧です。Railsなどのサードパーティgemはさらに多くの例外クラスを追加しますが、それらもすべてこの一覧のどこかのクラスを継承しています。

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit
 fatal
  1. OneDriveとOneDrive for Businessの違いとは?使い分けのポイントを徹底解説

    Microsoftのクラウドストレージサービス「OneDrive」を使えば、どこにいても自分のファイルにアクセスできます。しかし、実はMicrosoftは名前がよく似た2種類のOneDriveを提供しています。「OneDrive」と「OneDrive for Business」のどちらを使うべきかは、クラウドに何を保存するのか、そしてどうやってアクセスする必要があるのかによって変わってきます。 OneDrive(個人向け)の特徴 「OneDrive」は、個人ユーザー向けに設計されたMicrosoftのクラウドサービスです。個人のOutlookアカウントに含まれており、Windows 10では自

  2. Windows 10 HomeとProの違いを徹底解説!自分に合ったエディションの選び方

    新しいPCの購入を検討している場合でも、Windowsを単体で購入する場合でも、Windows 10 HomeとProの違いをあらかじめ把握しておくことが大切です。製品ページなどに特に表記がない場合は、そのデバイスにはWindows 10 Homeが搭載されていると考えてよいでしょう。 そもそも「Home」という名称はやや分かりにくく、実態としては「Windows 10 スタンダード」と呼ぶ方がぴったりです。価格1,000ポンド(約15万円)未満のWindowsデバイスの大半に搭載されており、OSのコア機能がすべて含まれています。 一方のProは、プロフェッショナル、パワーユーザー、ビジネ