LoginSignup
3
0

More than 3 years have passed since last update.

ruby 例外

Last updated at Posted at 2019-09-07

TL;DR

rescueのデフォルト引数は StandardError
rescueで捕捉できるのは、引数に指定した例外クラス or 引数に指定した例外クラスを継承したクラス
rescueでは StandardError を捕捉するべき


[1] pry(main)> begin
[1] pry(main)*   do_something()
[1] pry(main)* rescue => e
[1] pry(main)*   puts 'ERROR', e # e is an exception object containing info about the error.
[1] pry(main)* end
ERROR
undefined method `do_something' for main:Object
=> nil
[2] pry(main)> begin
[2] pry(main)*   do_something()
[2] pry(main)* rescue ActiveRecord::RecordNotFound => e
[2] pry(main)*   puts 'ERROR', e # Only rescues RecordNotFound exceptions, or classes that inherit from RecordNotFound
[2] pry(main)* end
NoMethodError: undefined method `do_something' for main:Object
from (pry):7:in `<main>'

上記2つの違いは rescue の引数に ActiveRecord::RecordNotFound の有無です。
指定しない場合は undefined method `do_something' for main:Object が捕捉されます。
指定した場合は捕捉されず、begin end後に例外が発生します。
なぜなら、 ActiveRecord::RecordNotFound を引数に指定した場合、 ActiveRecord::RecordNotFound か、 ActiveRecord::RecordNotFound を継承したエラーのみ、 rescue で捕捉することができるためです。
NoMethodError は、 ActiveRecord::RecordNotFound を継承していない、かつ、 ActiveRecord::RecordNotFound ではないので捕捉することができません。

補足すると、NoMethodErrorは
StandardError > NameError > NoMethodError
ActiveRecord::RecordNotFoundは
StandardError > ActiveRecord::ActiveRecordError
となっています。

話がそれましたが、たとえば NoMethodError の親である、 StandardErrorrescue に指定すれば捕捉することができます

[5] pry(main)> begin
[5] pry(main)*   do_something()
[5] pry(main)* rescue StandardError => e
[5] pry(main)*   puts 'ERROR', e # Only rescues RecordNotFound exceptions, or classes that inherit from RecordNotFound
[5] pry(main)* end
ERROR
undefined method `do_something' for main:Object
=> nil

さらに、 StandardError の親である、 Exception を指定しても同じです。

[9] pry(main)> begin
[9] pry(main)*   do_something()
[9] pry(main)* rescue Exception => e
[9] pry(main)*   puts 'ERROR', e # Only rescues RecordNotFound exceptions, or classes that inherit from RecordNotFound
[9] pry(main)* end
ERROR
undefined method `do_something' for main:Object
=> nil

Exception の親となる例外のクラスは存在しません。
だとすると、エラーの親である、 Exception を捕捉すればいいのではないかと考えますが、それは良くないです。
なぜなら、 Exception はシステムレベルの例外も含まれます。アプリケーションレベルの例外を捉えたいのではないでしょうか。
例として、ctrl + c で実行中のプログラムを終了する場合、そこで処理は終了するべきですが、このように書くと終了しません。

[10] pry(main)> 1.upto(10) do |i|
[10] pry(main)*   begin
[10] pry(main)*     p i
[10] pry(main)*     sleep(1)
[10] pry(main)*   rescue Exception => e
[10] pry(main)*     p e.class
[10] pry(main)*     p "catch!"
[10] pry(main)*   end
[10] pry(main)* end
1
2
3
^CInterrupt # ctrl + c を押した
"catch!"
4
5
^CInterrupt # ctrl + c を押した
"catch!"
6
7
^CInterrupt # ctrl + c を押した
"catch!"
8
9
^CInterrupt # ctrl + c を押した
"catch!"
10
=> 1

このような動作は想定していないと思います。

また、このような例もあります。 Exception を継承した、例外クラスを捕捉します。

[12] pry(main)> class MyError1 < Exception; end
=> nil
[13] pry(main)> begin
[13] pry(main)*   raise MyError1
[13] pry(main)* rescue => e
[13] pry(main)*   puts "Exception handled! #{e}"
[13] pry(main)* end
MyError1: MyError1
from (pry):32:in `<main>'

間違えました。これでは Exception を捕捉できません。

rescue に引数を指定しない場合、デフォルトで StandardError が引数になるからです。

今回の場合、引数を指定しないので、 StandardError が引数です。
Exception > StandardError
となっているため、 Exception を捕捉することができませんでした。なので

[15] pry(main)> class MyError1 < Exception; end
=> nil
[16] pry(main)> begin
[16] pry(main)*   raise MyError1
[16] pry(main)* rescue Exception => e
[16] pry(main)*   puts "Exception handled! #{e}"
[16] pry(main)* end
Exception handled! MyError1
=> nil

このようにすることで、 Exception を捕捉することができます。

参考にさせていただきました

https://qiita.com/ngron/items/4c319ae7340b72c7e566
http://ruby-doc.com/docs/ProgrammingRuby/
https://blog.toshimaru.net/ruby-standard-error/
https://qiita.com/tsubasakat/items/6825bcefcad26da3471b
https://www.honeybadger.io/blog/ruby-exception-vs-standarderror-whats-the-difference/

下記の通りすると、例外クラスを見ることができます。(2パターン)
1 @scivola さんに教えてくいただきました

exceptions = []
tree = {}
ObjectSpace.each_object(Class) do |cls|
  next unless cls.ancestors.include? Exception
  next if exceptions.include? cls
  next if cls.superclass == SystemCallError # avoid dumping Errno's
  exceptions << cls
  cls.ancestors.delete_if {|e| [Object, Kernel].include? e }.reverse.inject(tree) {|memo,cls| memo[cls] ||= {}}
end
tree_printer = Proc.new do |t, level = 0|
  t.sort_by { |c,| c.name }.each do |c, v|
    puts ('  ' * level) + c.to_s
    tree_printer[v, level + 1]
  end
end
tree_printer[tree]

2 http://blog.nicksieger.com/articles/2006/09/06/rubys-exception-hierarchy/

exceptions = []
tree = {}
ObjectSpace.each_object(Class) do |cls|
  next unless cls.ancestors.include? Exception
  next if exceptions.include? cls
  next if cls.superclass == SystemCallError # avoid dumping Errno's
  exceptions << cls
  cls.ancestors.delete_if {|e| [Object, Kernel].include? e }.reverse.inject(tree) {|memo,cls| memo[cls] ||= {}}
end

indent = 0
tree_printer = Proc.new do |t|
  t.keys.sort { |c1,c2| c1.name <=> c2.name }.each do |k|
    space = (' ' * indent); space ||= ''
    puts space + k.to_s
    indent += 2; tree_printer.call t[k]; indent -= 2
  end
end
tree_printer.call tree
BasicObject
  ActiveSupport::Tryable
    JSON::Ext::Generator::GeneratorMethods::Object
      PP::ObjectMixin
        ActiveSupport::Dependencies::Loadable
          ActiveSupport::ToJsonWithActiveSupportEncoder
            ActiveSupport::Dependencies::Blamable
              Exception
                CGI::InvalidEncoding
                EventMachine::FileNotFoundException
                IRB::Abort
                MonitorMixin::ConditionVariable::Timeout
                NoMemoryError
                OAuth::RequestProxy::UnknownRequestType
                OAuth::Signature::UnknownSignatureMethod
                Pry::InputLock::Interrupt
                ScriptError
                  LoadError
                    AbstractController::Helpers::MissingHelperError
                    ActionController::MissingRenderer
                    CodeRay::PluginHost::HostNotFound
                    CodeRay::PluginHost::PluginNotFound
                    FFI::NotFoundError
                    FFI::PlatformError
                    Gem::LoadError
                      Gem::ConflictError
                      Gem::MissingSpecError
                        Gem::MissingSpecVersionError
                  NotImplementedError
                  SyntaxError
                    Dotenv::FormatError
                SecurityError
                SignalException
                  Interrupt
                    Sidekiq::Shutdown
                StandardError
                  AbstractController::ActionNotFound
                  AbstractController::Error
                    AbstractController::DoubleRenderError
                  ActionController::ActionControllerError
                  ActiveModel::ValidationError
                  ActiveRecord::ActiveRecordError
                    ActiveRecord::RecordNotFound
                  NameError
                    NoMethodError
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~省略
                  Zlib::Error
                    Zlib::BufError
                    Zlib::DataError
                    Zlib::GzipFile::Error
                      Zlib::GzipFile::CRCError
                      Zlib::GzipFile::LengthError
                      Zlib::GzipFile::NoFooter
                    Zlib::MemError
                    Zlib::NeedDict
                    Zlib::StreamEnd
                    Zlib::StreamError
                    Zlib::VersionError
                SystemExit
                  Gem::SystemExitException
                SystemStackError
                fatal
=> [BasicObject]
3
0
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0