Rubyで自前の例外クラスを作るときExceptionではなくStandardErrorを継承する理由

Rubyの例外について少し調べたので、まとめてみる。

多くのモダンな言語同様にRubyでは例外処理機構が組み込まれている。

  • ファイルを開こうと思ったらファイルが存在しなかった
  • ネットワーク先のサーバが反応しなくてタイムアウトした
  • 定義されていない(存在しない)メソッドを呼んだ
  • 0で割り算をしてしまった

など想定外の問題に遭遇したときに、その問題を無視せずプログラマが何らかの対応処理をするための枠組みを提供する。

C言語など古い言語では、関数からの戻り値でエラーコードを返し、それによって呼び出し側がエラー処理をその場で記述する。例えば、fopen(3)が失敗すると戻り値としてNULLが戻ってきてグローバル変数のerrnoに失敗の理由を示すエラーコードが設定される。

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void) {

  FILE *fp;

  if ((fp = fopen("some_file_that_does_not_exist.txt", "r")) != NULL) {
      fputs(fp);
      fclose(fp);
  } else {
    printf("Error: %s\n", strerror(errno));
  }

  return 0;
}
$ cc error.c && ./a.out
Error: No such file or directory

C言語のような戻り値によるエラー処理に比べて、モダンな言語に備わる例外処理のメリットは、

  • エラー処理のコードを本体のコードと分けてまとめて記述できるので見通しが良くなる
  • 例外が発生すると基本的に処理系は止まる。いかにプログラマがずぼらであっても例外処理を記述することになるので、想定外の状況に対応できる堅牢なプログラムになる

というところか。

で、Rubyでは、QA@ITにあるこのQAにあるとおり例外クラスは以下のような階層になっている。Ruby 1.9.3の例。

Exception
  NoMemoryError
  ScriptError
    LoadError
      Gem::LoadError
    NotImplementedError
    SyntaxError
  SecurityError
  SignalException
    Interrupt
  StandardError
    ArgumentError
    EncodingError
      Encoding::CompatibilityError
      Encoding::ConverterNotFoundError
      Encoding::InvalidByteSequenceError
      Encoding::UndefinedConversionError
    FiberError
    IOError
      EOFError
    IndexError
      KeyError
      StopIteration
    LocalJumpError
    Math::DomainError
    NameError
      NoMethodError
    RangeError
      FloatDomainError
    RegexpError
    RuntimeError
      Gem::Exception
        Gem::CommandLineError
        Gem::DependencyError
        Gem::DependencyRemovalException
        Gem::DocumentError
        Gem::EndOfYAMLException
        Gem::FilePermissionError
        Gem::FormatException
        Gem::GemNotFoundException
        Gem::GemNotInHomeException
        Gem::InstallError
        Gem::InvalidSpecificationException
        Gem::OperationNotSupportedError
        Gem::RemoteError
        Gem::RemoteInstallationCancelled
        Gem::RemoteInstallationSkipped
        Gem::RemoteSourceException
        Gem::VerificationError
    SystemCallError
    ThreadError
    TypeError
    ZeroDivisionError
  SystemExit
    Gem::SystemExitException
  SystemStackError
  fatal

大事そうなのを抜き出すと、

Exception
  NoMemoryError
  ScriptError
    LoadError
    NotImplementedError
    SyntaxError
  SecurityError
  SignalException
    Interrupt
  StandardError
    ArgumentError
    EncodingError
    IOError
      EOFError
    IndexError
      KeyError
      StopIteration
    NameError
      NoMethodError
    RangeError
      FloatDomainError
    RegexpError
    RuntimeError
      Gem::Exception
    TypeError
    ZeroDivisionError
  SystemExit
  SystemStackError
  fatal

という感じかしら。

Rubyでは例外は自分でも定義できる。ただこのとき、ふつうは全ての例外クラスのスーパークラスであるExceptionクラスを継承しない。以下のようなケースで困ったことになるからだ。

class MyException < Exception; end

begin
  puts "hello"
  raise MyException
rescue
  puts "exception handled"
end

上の例で MyExceptionは補足されない。なぜなら、rescue は第1引数で指定した例外クラスの下の階層にある例外だけを補足するけど、引数を省略すると StandardErrorクラスを指定したものとみなすからだ。MyExceptionはException直下の子クラスなので、rescue されない。

Exceptionを直接継承した自前定義の例外クラスを複数作って、それらを補足しようと思うと、いちばん上にあるExceptionごと補足するしかない。そうすると今度は、すべての例外を補足することになってしまう。例えば、exitを呼ぶと発生するSystemExitも補足されてしまう。これは恐らく望ましい動作ではない。

class MyException < Exception; end
class MyException2 < Exception; end

begin
  puts "hello"
  exit
rescue Exception => ex
  p ex
  puts "exception handled"
end
$ ruby system-exceptions.rb
hello
#<SystemExit: exit>
exception handled
$

というわけで、自前の例外クラスを作るときには、システム関連の例外まで含むExceptionではなく、アプリケーションレベルのトップレベルとも言える例外クラスのStandardErrorを継承するのがいい。

class MyError < StandardError; end
class MyError2 < StandardError; end

begin
  puts "hello"
  raise MyError
rescue
  puts "exception handled" # 実行される
end

RuntimeErrorを継承するケースも良く見る。名前付けの習慣も、MyExceptionより、MyErrorのほうが多い。名前としては、ほかには InvalidHoge とか、NoHogeFoundとかもある。RuntimeErrorは、動作中の停止なので停止理由を動詞の過去形で書くのが多いようだ。例えばHerokuのCLIのgemを見たら、

class AppCrashed < RuntimeError; end
class CommandFailed  < RuntimeError; end

とRuntimeErrorを継承するシンプルな例外クラスが2つあるだけだった。シンプル。

自前で例外クラスを定義するとき、どういう風に階層化するのかという疑問もあり得る。Rubyじゃないけど、例えばこのC++のコーディングガイドには、注意深く例外クラスの階層を設計したところで利用者は誰も気にしないんだから時間の無駄だと書いてある。1つのライブラリもしくは名前空間に付いて1つの例外を定義すれば十分だ、と。Rubyでもmoduleの名前空間の階層に合わせてStandardErrorを継承することが多いようだ。

gemのトップディレクトリから以下のように調べてみた。

$ find . -iname '*rb' |xargs grep -h '^ *rescue ' | sed 's/^ *//' |sort | uniq -c |sort -nr
 718 rescue LoadError
 604 rescue Sass::SyntaxError => e
 445 rescue Exception => e
 235 rescue Sass::SyntaxError => err
 218 rescue LoadError => e
 206 rescue => e
 183 rescue Exception
 145 rescue NameError
 128 rescue ArgumentError
 125 rescue Errno::ENOENT
  89 rescue NameError => e
  60 rescue ArgumentError, TypeError
  58 rescue Interrupt
  57 rescue Exception => exception
  51 rescue NoMethodError => e
  50 rescue NoMethodError
  47 rescue Timeout::Error
  46 rescue ArgumentError => e
  36 rescue TypeError
  35 rescue Exception => ex
  33 rescue MemCache::MemCacheError => e
  32 rescue RestClient::RequestFailed => e
  32 rescue EOFError
  31 rescue LoadError => boom
  30 rescue RuntimeError => e
  29 rescue SyntaxError => e
  29 rescue NotImplementedError
  28 rescue StandardError => e
  27 rescue URI::InvalidURIError => e
  27 rescue SystemExit
  27 rescue RuntimeError
  27 rescue Errno::ECONNREFUSED
  25 rescue StandardError, ScriptError => e
  25 rescue => ex
  25 rescue ::Exception
  24 rescue TypeError, NoMethodError
  24 rescue Haml::Error => e
  22 rescue Test::Unit::AssertionFailedError => e
  22 rescue MemCache::MemCacheError
  22 rescue ::Test::Unit::AssertionFailedError => e
  21 rescue ThrowResult
  21 rescue ActionView::MissingTemplate => e
  20 rescue TZInfo::InvalidTimezoneIdentifier
  20 rescue Sass::UnitConversionError
  20 rescue EncodingError
  20 rescue ::Sass::SyntaxError => e
  19 rescue Gem::LoadError
  19 rescue => boom
  18 rescue RestClient::ResourceNotFound => e
  18 rescue InvalidNumberError => e
  18 rescue I18n::ArgumentError => e
  18 rescue Exception => database_transaction_rollback
  18 rescue Encoding::UndefinedConversionError => e
  17 rescue StandardError
  17 rescue Mysql::Error
  17 rescue LoadError => err
  16 rescue SystemCallError
  16 rescue PGError
  16 rescue Errno::ENOENT => e
  15 rescue Timeout::Error => e
  15 rescue Errno::ESRCH
  15 rescue Errno::EPIPE
  15 rescue ChildProcess::TimeoutError
  15 rescue ActiveRecord::StatementInvalid
  14 rescue SetupError
  14 rescue Errno::EACCES
  13 rescue IndexError
  12 rescue RuntimeError; end
  12 rescue LoadError => load_error
  12 rescue FSSM::FileNotRealError => e
  12 rescue Exception => e # errors from template code
  12 rescue Errno::EINPROGRESS
  12 rescue ActiveRecord::RecordNotFound
  11 rescue StandardError # JRuby
  11 rescue OpenSSLCipherError, TypeError
  11 rescue Object => e
  11 rescue NoMethodError then raise
  11 rescue NoMethodError                                # rescue NoMethodError
  11 rescue Mocha::ExpectationError => e
  11 rescue LocalJumpError
  11 rescue Exception => exception  # errors from loading file
  11 rescue ArgumentError => argument_error
  11 rescue ActiveRecord::StatementInvalid => exception
  11 rescue => frozen_object_error
  11 rescue => error
  11 rescue ::TZInfo::PeriodNotFound
  11 rescue ::NoMethodError
  10 rescue java.io.IOException => ex
  10 rescue TypeError, LoadError => e
  10 rescue ServerError => e
  10 rescue Sass::SyntaxError
  10 rescue Mysql::Error => e
  10 rescue MemCache::MemCacheError, Errno::ECONNREFUSED
  10 rescue Heroku::OkJson::Error
  10 rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
  10 rescue EOFError, TypeError, ArgumentError, LoadError => e
  10 rescue EOFError, TypeError, ArgumentError => e
  10 rescue ArgumentError # Encoding charset + endianness doesn't exist
  10 rescue *NOT_CONNECTED_ERRORS
  10 rescue 
   9 rescue ZipError
   9 rescue UnauthorizedAccess => e
   9 rescue Thor::Error, LoadError, Errno::ENOENT => e
   9 rescue ResourceInvalid => error
   9 rescue RescuableException => e
   9 rescue RescuableException
   9 rescue OpenSSL::SSL::SSLError => e
   9 rescue MissingSourceFile => e
   9 rescue LoadError, NameError => const_error
   9 rescue IOError
   9 rescue FixtureClassNotFound
   9 rescue Exception => failsafe_error
   9 rescue Exception => err
   9 rescue Exception => e # YAML, XML or Ruby code block errors
   9 rescue Errno::EWOULDBLOCK, Errno::EAGAIN
   9 rescue Errno::EEXIST
   9 rescue ActiveSupport::MessageVerifier::InvalidSignature
   9 rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
   9 rescue ActiveResource::ResourceNotFound
   9 rescue ActiveRecord::RecordInvalid
   9 rescue *RESCUE_ERRORS => error
   8 rescue TypeError => e
   8 rescue TimeoutError
   8 rescue SocketError, Errno::EADDRNOTAVAIL
   8 rescue RSpec::Mocks::MockExpectationError => e
   8 rescue Pry::RescuableException
   8 rescue NoMethodError, TypeError
   8 rescue LoadError, NameError
   8 rescue ImportError => e
   8 rescue I18n::ArgumentError
   8 rescue Heroku::API::Errors::RequestFailed => e
   8 rescue ::Timeout::Error => e
   8 rescue ::Exception => e
   8 rescue *PASSTHROUGH_EXCEPTIONS => e
   7 rescue SyntaxError
   7 rescue SwiftError => e
   7 rescue Sprockets::FileOutsidePaths
   7 rescue RestClient::Unauthorized
   7 rescue RestClient::BadRequest => e
   7 rescue Racc::ParseError => e
   7 rescue PGError => e
   7 rescue OptionParser::InvalidOption => ex
   7 rescue OpenURI::HTTPError
   7 rescue Netrc::Error
   7 rescue LoadError; end
   7 rescue Exception => error
   7 rescue Errno::ENOTEMPTY
   7 rescue Errno::ECHILD, Errno::ESRCH
   7 rescue Encoding::ConverterNotFound => _
   7 rescue ActionView::MissingTemplate
   7 rescue ::MultiJson::DecodeError => e
   7 rescue *RESCUE_ERRORS
   6 rescue ZeroDivisionError
   6 rescue URI::InvalidURIError
   6 rescue Thor::Error => e
   6 rescue SystemCallError => exception
   6 rescue RestClient::RequestTimeout
   6 rescue Rack::Test::Error
   6 rescue OpenSSL::SSL::SSLError => error
   6 rescue LoadError => ignore
   6 rescue Journey::Router::RoutingError
   6 rescue Johnson::Error => e
   6 rescue JSON::ParserError => e
   6 rescue JSON::NestingError
   6 rescue IOError, Errno::EPIPE
   6 rescue I18n::MissingTranslationData => exception
   6 rescue Heroku::Client::AppCrashed => e
   6 rescue Haml::SyntaxError => e
   6 rescue Exception  # errors from Marshal or YAML
   6 rescue Errno::EPERM => e
   6 rescue Errno::ENOSPC
   6 rescue Errno::ENOENT, IOError
   6 rescue Errno::ENOENT, Errno::ELOOP
   6 rescue Errno::EINVAL
   6 rescue Errno::ECHILD
   6 rescue Errno::EAGAIN
   6 rescue Errno::EADDRINUSE
   6 rescue Dalli::DalliError
   6 rescue DRb::DRbConnError
   6 rescue CallbackError => e
   6 rescue ArgumentError # if Date.new raises an exception on an invalid date
   6 rescue ::V8::JSError => e
   5 rescue java.lang.IllegalThreadStateException
   5 rescue Win32::Registry::Error
   5 rescue Timeout::Error, EOFError
   5 rescue SystemCallError => ex
   5 rescue SocketError, Errno::EADDRINUSE, Errno::EBADF => ex
   5 rescue SocketError, Errno::EADDRINUSE
   5 rescue ScriptError, StandardError => e
   5 rescue RestClient::SSLCertificateNotVerified => ex
   5 rescue RestClient::Locked => ex
   5 rescue RestClient::BadGateway => e
   5 rescue RSpec::Expectations::ExpectationNotMetError => last_error
   5 rescue ProtocolError => why
   5 rescue OptionParser::InvalidOption => e
   5 rescue Object
   5 rescue Nokogiri::XML::XPath::SyntaxError => e
   5 rescue NameError, ArgumentError => e
   5 rescue Interrupt => interrupt
   5 rescue Heroku::Command::CommandFailed
   5 rescue Gem::LoadError => e
   5 rescue Errno::EISCONN
   5 rescue Errno::EIO
   5 rescue Errno::ECONNREFUSED, Errno::ECONNRESET, OpenSSL::SSL::SSLError
   5 rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EADDRINUSE
   5 rescue Dalli::DalliError => e
   5 rescue ConnectionError
   5 rescue CommandFailed => e
   5 rescue ChildProcess::MissingPlatformError => ex
   5 rescue ChildProcess::Error
   5 rescue Bundler::BundlerError => e
   5 rescue => err
   5 rescue ::OAuth::Unauthorized => e
   5 rescue *exceptions => ex
   5 rescue *QUIT_ERRORS
   5 rescue *CONNECTED_ERRORS
   4 rescue Utf8Error
   4 rescue Undefined => e
   4 rescue TypeError, ArgumentError
   4 rescue SystemCallError, SocketError => e
   4 rescue SignalException => e
   4 rescue Sequel::DatabaseDisconnectError
   4 rescue RuntimeException => e
   4 rescue RSpec::Mocks::MockExpectationError => error
   4 rescue RSpec::Expectations::ExpectationNotMetError
   4 rescue PostTooBigException
   4 rescue Pending::PendingDeclaredInExample => e
   4 rescue OAuth::Unauthorized => e
   4 rescue Nokogiri::XML::XPath::SyntaxError
   4 rescue Nokogiri::SyntaxError, RuntimeError
   4 rescue NoMethodError, ArgumentError
   4 rescue NewRelic::Command::CommandFailure => e
   4 rescue NewRelic::Agent::ForceRestartException => e
   4 rescue Net::LDAP::LdapError
   4 rescue NameError => predicate_missing_error
   4 rescue MultiJson::DecodeError => de
   4 rescue Memcached::NotFound
   4 rescue Mail::Field::ParseError => e
   4 rescue LoadError # 1.8 support
   4 rescue Iconv::InvalidEncoding => e
   4 rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
   4 rescue Iconv::IllegalSequence => e
   4 rescue I18n::ArgumentError => exception
   4 rescue Heroku::API::Errors::NotFound, Heroku::API::Errors::Unauthorized => e
   4 rescue Heroku::API::Errors::NotFound => e
   4 rescue Heroku::API::Errors::ErrorWithResponse => e
   4 rescue GemNotFound => e
   4 rescue Gem::RemoteFetcher::FetchError
   4 rescue Gem::GemNotFoundException
   4 rescue FFI::NotFoundError
   4 rescue Exception => raised
   4 rescue Exception => e # Net::SMTP errors or sendmail pipe errors
   4 rescue Exception => @actual_error
   4 rescue Error::NoSuchElementError => last_error
   4 rescue Errno::EWOULDBLOCK
   4 rescue Errno::ESRCH => e
   4 rescue Errno::EHOSTDOWN
   4 rescue Errno::EAGAIN, Errno::EINTR
   4 rescue Errno::EACCES => e
   4 rescue EncodingFound => e
   4 rescue CmdException, OptionParser::ParseError => e
   4 rescue Capistrano::ConnectionError => e
   4 rescue Capistrano::CommandError => e
   4 rescue Bundler::GemNotFound => e
   4 rescue @expected_exception => @rescued_exception
   4 rescue @expected_error => @actual_error
   4 rescue => other_exception
   4 rescue ::Timeout::Error, ::Errno::ETIMEDOUT => e
   4 rescue ::Rhino::JavascriptError => e
   4 rescue ::Net::HTTPFatalError => e
   4 rescue ::Memcached::NotFound
   4 rescue ::Kramdown::Error
   4 rescue ::Exception # work around a bug in ruby 1.9
   4 rescue ::Exception   # for example on EPERM (process exists but does not belong to us)
   3 rescue get_exception(arg)
   3 rescue get_exception(0), NameError
   3 rescue Zlib::BufError
   3 rescue ValueError, msg:
   3 rescue V8::JSError => e
   3 rescue SystemExit => se
   3 rescue StandardError, ScriptError
   3 rescue StandardError, LoadError, SyntaxError => e
   3 rescue Sprockets::FileNotFound, Sprockets::ContentTypeMismatch
   3 rescue Spec::Expectations::ExpectationNotMetError => e
   3 rescue SignalException
   3 rescue ServerError => why
   3 rescue Sequel::NotImplemented
   3 rescue SecurityError
   3 rescue RuntimeError => why
   3 rescue Rhino::JSError => e
   3 rescue RestClient::Locked => e
   3 rescue Rack::Mount::RoutingError
   3 rescue RSpec::Expectations::ExpectationNotMetError => e
   3 rescue OptionParser::ParseError => ex
   3 rescue OptionParser::ParseError => err
   3 rescue OptionParser::ParseError => e
   3 rescue NotFoundError => e
   3 rescue NoMethodError, Haml::Util.av_template_class(:Error)
   3 rescue Net::SSH::AuthenticationFailed
   3 rescue NativeException, JavaSQL::SQLException => e
   3 rescue NamespaceMissingError
   3 rescue NameError, LoadError => e
   3 rescue MissingTranslationData
   3 rescue Message::KeyNotFound => why
   3 rescue Magick::ImageMagickError
   3 rescue LoadError; end # RCov doesn't see this, but it is run
   3 rescue LoadError, SyntaxError => ex
   3 rescue LoadError, NameError => error
   3 rescue LoadError, NameError => e
   3 rescue LoadError => ignore_if_database_cleaner_not_present
   3 rescue LoadError => error
   3 rescue LoadError => cannot_require
   3 rescue LoadError # => rubygems_not_installed
   3 rescue LoadError # => gem_not_installed
   3 rescue InvalidValue
   3 rescue InvalidURIError, TypeError
   3 rescue Interrupt => e
   3 rescue I18n::MissingTranslationData
   3 rescue Haml::Util.av_template_class(:Error) => e
   3 rescue ForwardRequest => req
   3 rescue FileNotFound
   3 rescue ExecutionError => e
   3 rescue Excon::Errors::NotFound
   3 rescue Exception => exp
   3 rescue Exception => e  # errors from Marshal or YAML
   3 rescue Exception =>  e
   3 rescue Errno::ETIMEDOUT
   3 rescue Errno::ESPIPE
   3 rescue Errno::EOPNOTSUPP
   3 rescue Errno::ENOENT, Errno::ENOTDIR
   3 rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable
   3 rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
   3 rescue Encoding::ConverterNotFoundError => _
   3 rescue Bundler::RubyVersionMismatch => e
   3 rescue ArgumentError, NameError
   3 rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
   3 rescue AgentProxyException => e
   3 rescue => socket_error
   3 rescue => request_error
   3 rescue => details
   3 rescue ::OptionParser::ParseError => pe
   3 rescue ::Haml::Error => e
   3 rescue ::Excon::Errors::SocketError
   3 rescue ::Exception => boom
   3 rescue *exceptions => @rescued_exception
   3 rescue # else leave as is
   2 rescue parser.parse_error => error
   2 rescue engine::ParseError => exception
   2 rescue adapter::ParseError => exception
   2 rescue YARD::Parser::UndocumentableError => err
   2 rescue UnserializeError
   2 rescue Timeout::Error, StandardError => e
   2 rescue Timeout::Error, StandardError
   2 rescue Timeout::Error, NewRelic::Agent::ServerConnectionException
   2 rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
   2 rescue Timeout::Error => err
   2 rescue TemplateOperatorAbortedError
   2 rescue Taps::CorruptedData => e
   2 rescue Tags::TagFormatError
   2 rescue SystemExit, NoMemoryError, SignalException
   2 rescue SystemExit => ex
   2 rescue SystemExit => e
   2 rescue SystemCallError => er
   2 rescue SyntaxError, RegexpError
   2 rescue SyntaxError, NoMethodError
   2 rescue SyntaxError, NameError
   2 rescue SyntaxError => ex
   2 rescue StandardError, Timeout::Error
   2 rescue StandardError => ex
   2 rescue StandardError => err
   2 rescue Spork::TestFramework::NoFrameworksAvailable => e
   2 rescue Spork::TestFramework::FactoryException => e
   2 rescue SocketError
   2 rescue Server::ProtocolError => err
   2 rescue Sequel::InvalidValue => e
   2 rescue ScriptError, StandardError => error
   2 rescue ScriptError, RegexpError, NameError, ArgumentError => e
   2 rescue Sass::SyntaxError => keyword_exception
   2 rescue RuntimeError => err
   2 rescue RunnerError => e
   2 rescue RubyPython::PythonError => exc
   2 rescue RestClient::Unauthorized, Heroku::API::Errors::Unauthorized
   2 rescue RestClient::RequestTimeout, Heroku::API::Errors::Timeout
   2 rescue RestClient::Request::Unauthorized => e
   2 rescue RestClient::PaymentRequired, Heroku::API::Errors::VerificationRequired => e
   2 rescue RestClient::Locked, Heroku::API::Errors::Locked => e
   2 rescue RestClient::Exception
   2 rescue RemoteError => error
   2 rescue RbSupport::NilWorld => e
   2 rescue Rack::AdapterNotFound => e
   2 rescue Racc::ParseError, Regin::Parser::ScanError
   2 rescue QuestionError
   2 rescue Question::NoAutoCompleteMatch
   2 rescue PythonError, NoMethodError
   2 rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
   2 rescue PluginHost::PluginNotFound
   2 rescue Pending => e
   2 rescue PathError, GitError
   2 rescue PGError, NoMethodError
   2 rescue OptionParser::ParseError
   2 rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
   2 rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError => e
   2 rescue OpenSSL::PKey::ECError => e
   2 rescue OpenID::DiscoveryFailure => e
   2 rescue Object # Ignore since ObjectSpace might not be loaded on JRuby
   2 rescue NotSupportedByDriverError
   2 rescue NotImplementedError => ex
   2 rescue NoMethodError => pre_cucumber_0_4 # REMOVE WHEN SUPPORT FOR PRE-0.4 IS DROPPED
   2 rescue NilWorld => e
   2 rescue NewRelic::Command::CommandFailure => c
   2 rescue NewRelic::Command::CommandFailure
   2 rescue NewRelic::Agent::ServerConnectionException => e
   2 rescue NewRelic::Agent::Sampler::Unsupported => e
   2 rescue NewRelic::Agent::LicenseException => e
   2 rescue NewRelic::Agent::ForceRestartException, NewRelic::Agent::ForceDisconnectException
   2 rescue NewRelic::Agent::ForceDisconnectException => e
   2 rescue Net::HTTPBadResponse => e
   2 rescue Net::HTTP::Persistent::Error => error
   2 rescue NameError => name_error
   2 rescue LoadError, NotImplementedError
   2 rescue LoadError => try_rspec_1_2_4_or_higher
   2 rescue LoadError => try_rspec_1
   2 rescue LoadError => loadError
   2 rescue LoadError => give_up
   2 rescue LoadError => ex
   2 rescue LoadError                  # If we're not on Windows try...
   2 rescue LoadError
   2 rescue InvalidRequest => e
   2 rescue Interrupt, StandardError, SystemExit => error
   2 rescue Interrupt, IOError
   2 rescue IOError, EOFError, Timeout::Error,
   2 rescue IOError => e
   2 rescue Heroku::API::Errors::VerificationRequired, RestClient::PaymentRequired => e
   2 rescue Heroku::API::Errors::Unauthorized, RestClient::Unauthorized
   2 rescue Heroku::API::Errors::Timeout, RestClient::RequestTimeout
   2 rescue Heroku::API::Errors::Locked => e
   2 rescue HTTParty::RedirectionTooDeep, Timeout::Error => e
   2 rescue HTTPError, TypeError => e
   2 rescue GitError
   2 rescue Gherkin::Lexer::LexingError, Gherkin::Parser::ParseError => e
   2 rescue GemNotFound, VersionConflict
   2 rescue Gem::Package::FormatError
   2 rescue Gem::InvalidSpecificationException => e
   2 rescue ForkDiedException, EOFError
   2 rescue FetchingError => why
   2 rescue Faraday::Error::ClientError
   2 rescue Excon::Errors::StubNotFound, Excon::Errors::Timeout => error
   2 rescue Excon::Errors::HTTPStatusError => error
   2 rescue Errno::EWOULDBLOCK, Errno::EAGAIN => e
   2 rescue Errno::ESRCH # No such process
   2 rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED
   2 rescue Errno::EPIPE => e
   2 rescue Errno::EPERM
   2 rescue Errno::EMFILE
   2 rescue Errno::EFBIG # FreeBSD 4.9 raise Errno::EFBIG instead of Errno::EINVAL
   2 rescue Errno::EFBIG # FreeBSD 4.5 may raise Errno::EFBIG
   2 rescue Errno::ECONNRESET => e
   2 rescue Errno::ECONNREFUSED => exception
   2 rescue Errno::EBADF
   2 rescue Errno::EAGAIN, Errno::EWOULDBLOCK
   2 rescue Errno::EADDRINUSE => e
   2 rescue Errno::EACCES, Errno::ENOENT
   2 rescue EOFError => e
   2 rescue EOFError  # HighLine throws this if @input.eof?
   2 rescue DiscoveryFailure => why
   2 rescue DalliError, NetworkError => e
   2 rescue Dalli::NetworkError
   2 rescue DRbClientError => e
   2 rescue DRb::DRbConnError => e
   2 rescue Cucumber::Ast::Table::Different => e
   2 rescue Cucumber::Ast::Table::Different
   2 rescue Cucumber::ArityMismatchError => e
   2 rescue ConnectionError => error
   2 rescue Compass::Error
   2 rescue CommandOptionError => ex
   2 rescue Capybara::TimeoutError; end
   2 rescue Capybara::ExpectationNotMet
   2 rescue Capistrano::NoMatchingServersError, Capistrano::NoSuchTaskError => error
   2 rescue Capistrano::Error => error
   2 rescue Capistrano::CommandError
   2 rescue BundlerError
   2 rescue Bundler::VersionConflict => e
   2 rescue Bundler::GitError => e
   2 rescue Bundler::GemNotFound
   2 rescue ArgumentError, TypeError => e
   2 rescue ArgumentError, SyntaxError, Gem::EndOfYAMLException, Gem::Exception
   2 rescue ArgumentError, NameError => error
   2 rescue ArgumentError => why
   2 rescue ArgumentError => boom
   2 rescue Ambiguous => e
   2 rescue ActiveRecord::StatementInvalid => e
   2 rescue ActiveRecord::ActiveRecordError => e
   2 rescue => foo
   2 rescue ::WIN32OLERuntimeError => e
   2 rescue ::SocketError => e
   2 rescue ::Rhino::JSError => e
   2 rescue ::Patron::TimeoutError => err
   2 rescue ::Object
   2 rescue ::ODBC::Error, ArgumentError => e
   2 rescue ::OAuth2::HTTPError, ::OAuth2::AccessDenied, CallbackError => e
   2 rescue ::OAuth2::HTTPError => e
   2 rescue ::OAuth2::Error, CallbackError => e
   2 rescue ::NoMethodError, ::MultiJson::DecodeError => e
   2 rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
   2 rescue ::Memcached::NotStored
   2 rescue ::GrowlNotify::GrowlNotFound
   2 rescue ::Errno::ETIMEDOUT
   2 rescue ::DataObjects::Error => e
   2 rescue *NET_HTTP_EXCEPTIONS
   2 rescue *HTTP_ERRORS => e
   2 rescue *EXCEPTIONS => e
   1 rescue stub_exception_class
   1 rescue exception => @rescued_exception
   1 rescue error => ex
   1 rescue Zlib::GzipFile::Error
   1 rescue Zlib::Error
   1 rescue Zlib::DataError
   1 rescue Yadis::XRDSError => why
   1 rescue Yadis::XRDSError
   1 rescue XRDSError => err
   1 rescue WebSocketError => e
   1 rescue UndocumentableError => err
   1 rescue URIClassifier::RegistrationError => e
   1 rescue URI::InvalidURIError => why
   1 rescue URI::InvalidURIError => err
   1 rescue URI::Error => why
   1 rescue URI::Error => e
   1 rescue TypeURIMismatch
   1 rescue TypeError
   1 rescue Timeout::Error => why
   1 rescue Test::Unit::AssertionFailedError => err
   1 rescue TerminateLineInput
   1 rescue Taps::DuplicatePrimaryKeyError => e
   1 rescue TagFormatError
   1 rescue SystemStackError
   1 rescue SystemCallError; end
   1 rescue SystemCallError, TypeError
   1 rescue SystemCallError, Timeout::Error, EOFError, SocketError
   1 rescue SystemCallError, Timeout::Error, EOFError
   1 rescue SystemCallError, Timeout::Error
   1 rescue SyntaxError => e2
   1 rescue StopServer
   1 rescue Sprockets::FileNotFound
   1 rescue SocketError => err
   1 rescue Slim::Parser::SyntaxError => ex
   1 rescue Server::UntrustedReturnURL => err
   1 rescue Server::MalformedTrustRoot => why
   1 rescue Sequel::UndefinedAssociation
   1 rescue Sequel::Rollback
   1 rescue Sequel::DatabaseError=>e
   1 rescue Sequel::DatabaseError => e
   1 rescue Sequel::DatabaseError
   1 rescue Sequel::DatabaseConnectionError
   1 rescue Selenium::WebDriver::Error::WebDriverError
   1 rescue Selenium::WebDriver::Error::UnhandledError => e
   1 rescue SQLite3::Exception => e
   1 rescue RuntimeError => exc
   1 rescue RuntimeError => ex
   1 rescue RubyVersionMismatch => e
   1 rescue RubyPython::PythonError => e
   1 rescue RestClient::PaymentRequired => e
   1 rescue RestClient::Exception, Taps::BaseError => e
   1 rescue ResponseError => error
   1 rescue Redis::CannotConnectError
   1 rescue RealmVerificationRedirected => err
   1 rescue RangeError
   1 rescue Rack::Lint::LintError => e
   1 rescue RDoc::RubyLex::Error
   1 rescue RDoc::Markup::Parser::Error => e
   1 rescue RDoc::Error
   1 rescue PythonError => exc
   1 rescue PythonError => e
   1 rescue Psych::SyntaxError
   1 rescue ProtocolError => e
   1 rescue Polyglot::NestedLoadError => e
   1 rescue ParserSyntaxError => e
   1 rescue ParserError
   1 rescue Parser::UndocumentableError => undocerr
   1 rescue Parser::ParserSyntaxError
   1 rescue PGError =>e
   1 rescue OptionParser::InvalidOption
   1 rescue OpenURI::HTTPRedirect => e
   1 rescue OpenURI::HTTPError => e
   1 rescue OpenSSL::SSL::SSLError => why
   1 rescue OpenSSL::PKey::PKeyError
   1 rescue OpenIDError => why
   1 rescue OpenID::OpenIDError => e
   1 rescue OpenID::FetchingError => why
   1 rescue OkJson::Parser
   1 rescue Object; end }
   1 rescue Object => rails_error
   1 rescue Object => exc
   1 rescue Object => boom
   1 rescue Object => anything
   1 rescue Object # Ignore since obj.class can sometimes take parameters            
   1 rescue OCIInvalidHandle
   1 rescue OCIException => e
   1 rescue NotImplementedError, NameError
   1 rescue NotFoundError
   1 rescue NotFound => boom
   1 rescue NonMethodContextError => err
   1 rescue NoSuchKey
   1 rescue NoMethodError => no_method_error
   1 rescue NoMethodError => ex
   1 rescue NetworkError => e
   1 rescue Net::SSH::Authentication::DisallowedMethod
   1 rescue NativeException => e
   1 rescue NamespaceMissingError => missingerr
   1 rescue NamespaceAliasRegistrationError => e
   1 rescue NameError, NoMethodError
   1 rescue NameError => ne
   1 rescue NameError => ex
   1 rescue Mongo::ConnectionFailure
   1 rescue MissingTranslationData => exception
   1 rescue Message::KeyNotFound, ArgumentError => why
   1 rescue LoadError, RuntimeError => e
   1 rescue LoadError, RuntimeError
   1 rescue LoadError, ArgumentError => error
   1 rescue LoadError => load_exception
   1 rescue LoadError => library_not_installed
   1 rescue LoadError => e2
   1 rescue LoadError => e 
   1 rescue LoadError # try rspec 1
   1 rescue LoadError            # If our first choice fails, try using ffi-ncurses.
   1 rescue LoadError            # Finally, if all else fails, use stty
   1 rescue LoadError                # If the ffi-ncurses choice fails, try using stty
   1 rescue LoadError                # If our first choice fails, try using JLine
   1 rescue LoadError 
   1 rescue LibraryNotPreparedError
   1 rescue Less::ParseError => e
   1 rescue KVFormError => err
   1 rescue JSON::ParserError
   1 rescue InvalidOpenIDNamespace => e
   1 rescue InvalidOpenIDNamespace
   1 rescue Interrupt, StandardError, RDoc::Error, SystemStackError => e
   1 rescue InternalError, RequestTimeout
   1 rescue IncompleteExpression
   1 rescue ImageMagickError
   1 rescue IOError => e                                 
   1 rescue HttpParserError => e
   1 rescue Heroku::Plugin::ErrorUpdatingSymlinkPlugin
   1 rescue Heroku::API::Errors::Error
   1 rescue HandshakeError => e
   1 rescue HTTPStatusError => why
   1 rescue HTTPRedirectLimitReached => e
   1 rescue HTMLTokenizerError # just stop parsing if there's an error
   1 rescue Gem::LoadError, LoadError, RuntimeError
   1 rescue Gem::LoadError => load_error
   1 rescue Gem::Exception
   1 rescue Gem::DocumentError => e
   1 rescue Foreman::Export::Exception => ex
   1 rescue FloatDomainError
   1 rescue FinishRequest
   1 rescue FilterNotFound
   1 rescue FSSM::CallbackError => e
   1 rescue Excon::Errors::StubNotFound => stub_not_found
   1 rescue Excon::Errors::SocketError => error
   1 rescue Excon::Errors::Error => error
   1 rescue Exception=>exception
   1 rescue Exception, OpenSSL::OpenSSLError => e 
   1 rescue Exception => why
   1 rescue Exception => e  # errors from ActiveRecord setup
   1 rescue Exception # just stop parsing if there's an error
   1 rescue EventMachine::ConnectionError => e
   1 rescue Error => e
   1 rescue Error
   1 rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
   1 rescue Errno::EPIPE, RestClient::RequestFailed, RestClient::RequestTimeout
   1 rescue Errno::EPIPE, Errno::ECONNRESET
   1 rescue Errno::EPIPE => boom
   1 rescue Errno::ENOTCONN => e
   1 rescue Errno::ENOENT => e  
   1 rescue Errno::ENETUNREACH
   1 rescue Errno::EISDIR, Errno::ENOENT
   1 rescue Errno::EINVAL, Errno::EBADF
   1 rescue Errno::EDOM
   1 rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
   1 rescue Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError
   1 rescue Errno::ECONNRESET, EOFError
   1 rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
   1 rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error => error
   1 rescue Errno::ECONNREFUSED, Errno::EBADF
   1 rescue Errno::ECONNREFUSED => ex
   1 rescue Errno::ECONNREFUSED => e
   1 rescue Errno::ECONNABORTED
   1 rescue Errno::EADDRNOTAVAIL
   1 rescue Errno::EACCES; end
   1 rescue Errno::EACCES => error
   1 rescue Errno::EACCES # unreadble file
   1 rescue EncodingFoundException => e
   1 rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
   1 rescue EOFError, Errno::ESPIPE
   1 rescue EOFError, Errno::ECONNRESET, Errno::ECONNREFUSED
   1 rescue EOFError, Errno::ECONNRESET
   1 rescue DatabaseError
   1 rescue Dalli::MarshalError => ex
   1 rescue Dalli::DalliError => ex
   1 rescue Dalli::DalliError # SASL auth failure
   1 rescue Conversion::UnsupportedConversion => exc
   1 rescue ContextMiss
   1 rescue ConnectionError => e
   1 rescue Compass::Error => e
   1 rescue CommandError, Slop::InvalidOptionError => e
   1 rescue CommandError
   1 rescue ChannelRequestFailed
   1 rescue ChannelOpenFailed => err
   1 rescue BeforeHookFailed 
   1 rescue BSON::InvalidObjectId, ::Mongoid::Errors::DocumentNotFound
   1 rescue BSON::InvalidObjectId
   1 rescue ArgumentError, TypeError, RangeError => error
   1 rescue ArgumentError, NotImplementedError => e
   1 rescue ArgumentError => err
   1 rescue ArgumentError # for ruby < 1.9 compat
   1 rescue ArgumentError # bad timestamp
   1 rescue ArgumentError # If HOME is relative
   1 rescue AgentNotAvailable
   1 rescue @container::ResponseError => e
   1 rescue @container::AccessDenied => e
   1 rescue => e2
   1 rescue => detail
   1 rescue => @e
   1 rescue ::StandardError => e
   1 rescue ::RestClient::Unauthorized, ::RestClient::ResourceNotFound => e
   1 rescue ::OpenID::OpenIDError, Timeout::Error => e
   1 rescue ::OmniAuth::NoSessionError => e
   1 rescue ::ODBC::Error => e
   1 rescue ::Mysql2::Error => e
   1 rescue ::LoadError
   1 rescue ::Hpricot::Error, RuntimeError, ArgumentError
   1 rescue ::Amalgalite::Error, ::Amalgalite::SQLite3::Error => e
   1 rescue ::ActionView::MissingTemplate => exception
   1 rescue ::AWS::S3::NoSuchBucket
   1 rescue *parse_error
   1 rescue *@ignored => last_error
   1 rescue # ignore failed gsub, for instance when non-utf8
   1 rescue  ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
   1 rescue  # Defend against inspect not taking a parameter

LoadErrorが多いのはgemの性質から来るものかしら。

例外オブジェクトに自前メッセージや自前スタックトレースを付け加えることもできるけど、例外クラス名そのものがズバリのエラーの原因を示している、という例も多い。というか、それが多数派。

以下のようにRSpecを調べてみたら、「RSpec::Configuration::MustBeConfiguredBeforeExampleGroupsError < StandardError」なんていうのもある。なるほど。

$ find gems/rspec-*11* -name '*rb' |xargs grep -hi -C10 'class.*<.*Error'

    #       c.before(:suite) { establish_connection }
    #       c.before(:each)  { log_in_as :authorized }
    #       c.around(:each)  { |ex| Database.transaction(&ex) }
    #     end
    #
    # @see RSpec.configure
    # @see Hooks
    class Configuration
      include RSpec::Core::Hooks

      class MustBeConfiguredBeforeExampleGroupsError < StandardError; end

      # @private
      def self.define_reader(name)
        eval <<-CODE
          def #{name}
            value_for(#{name.inspect}, defined?(@#{name}) ? @#{name} : nil)
          end
        CODE
      end

--
module RSpec
  module Core
    module Pending
      class PendingDeclaredInExample < StandardError; end

      # If Test::Unit is loaed, we'll use its error as baseclass, so that Test::Unit
      # will report unmet RSpec expectations as failures rather than errors.
      begin
        class PendingExampleFixedError < Test::Unit::AssertionFailedError; end
      rescue
        class PendingExampleFixedError < StandardError; end
      end

      class PendingExampleFixedError
        def pending_fixed?; true; end
      end

      NO_REASON_GIVEN = 'No reason given'
      NOT_YET_IMPLEMENTED = 'Not yet implemented'

      # @overload pending()
--
    #       c.before(:suite) { establish_connection }
    #       c.before(:each)  { log_in_as :authorized }
    #       c.around(:each)  { |ex| Database.transaction(&ex) }
    #     end
    #
    # @see RSpec.configure
    # @see Hooks
    class Configuration
      include RSpec::Core::Hooks

      class MustBeConfiguredBeforeExampleGroupsError < StandardError; end

      # @private
      def self.define_reader(name)
        eval <<-CODE
          def #{name}
            value_for(#{name.inspect}, defined?(@#{name}) ? @#{name} : nil)
          end
        CODE
      end

--
module RSpec
  module Core
    module Pending
      class PendingDeclaredInExample < StandardError; end

      # If Test::Unit is loaed, we'll use its error as baseclass, so that Test::Unit
      # will report unmet RSpec expectations as failures rather than errors.
      begin
        class PendingExampleFixedError < Test::Unit::AssertionFailedError; end
      rescue
        class PendingExampleFixedError < StandardError; end
      end

      class PendingExampleFixedError
        def pending_fixed?; true; end
      end

      NO_REASON_GIVEN = 'No reason given'
      NOT_YET_IMPLEMENTED = 'Not yet implemented'

      # @overload pending()
--
module RSpec
  module Expectations
    if defined?(Test::Unit::AssertionFailedError)
      class ExpectationNotMetError < Test::Unit::AssertionFailedError; end
    else
      class ExpectationNotMetError < ::StandardError; end
    end
  end
end
--
require 'spec_helper'

class UnexpectedError < StandardError; end
module MatcherHelperModule
  def self.included(base)
    base.module_eval do
      def included_method; end
    end
  end

  def self.extended(base)
    base.instance_eval do
      def extended_method; end
--
module RSpec
  module Expectations
    if defined?(Test::Unit::AssertionFailedError)
      class ExpectationNotMetError < Test::Unit::AssertionFailedError; end
    else
      class ExpectationNotMetError < ::StandardError; end
    end
  end
end
--
require 'spec_helper'

class UnexpectedError < StandardError; end
module MatcherHelperModule
  def self.included(base)
    base.module_eval do
      def included_method; end
    end
  end

  def self.extended(base)
    base.instance_eval do
      def extended_method; end
--
module RSpec
  module Mocks
    # @private
    class MockExpectationError < Exception
    end
    
    # @private
    class AmbiguousReturnError < StandardError
    end
  end
end

--
require 'spec_helper'

module RSpec
  module Mocks
    describe "#any_instance" do
      class CustomErrorForAnyInstanceSpec < StandardError;end

      let(:klass) do
        Class.new do
          def existing_method; :existing_method_return_value; end
          def existing_method_with_arguments(arg_one, arg_two = nil); :existing_method_with_arguments_return_value; end
          def another_existing_method; end
          private
          def private_method; :private_method_return_value; end
        end
      end
--

      it "raises instance of submitted ArgumentError" do
        error = ArgumentError.new("error message")
        @double.should_receive(:something).and_raise(error)
        lambda {
          @double.something
        }.should raise_error(ArgumentError, "error message")
      end

      it "fails with helpful message if submitted Exception requires constructor arguments" do
        class ErrorWithNonZeroArgConstructor < RuntimeError
          def initialize(i_take_an_argument)
          end
        end

        @double.stub(:something).and_raise(ErrorWithNonZeroArgConstructor)
        lambda {
          @double.something
        }.should raise_error(ArgumentError, /^'and_raise' can only accept an Exception class if an instance/)
      end

--
module RSpec
  module Mocks
    # @private
    class MockExpectationError < Exception
    end
    
    # @private
    class AmbiguousReturnError < StandardError
    end
  end
end

--
require 'spec_helper'

module RSpec
  module Mocks
    describe "#any_instance" do
      class CustomErrorForAnyInstanceSpec < StandardError;end

      let(:klass) do
        Class.new do
          def existing_method; :existing_method_return_value; end
          def existing_method_with_arguments(arg_one, arg_two = nil); :existing_method_with_arguments_return_value; end
          def another_existing_method; end
          private
          def private_method; :private_method_return_value; end
        end
      end
--

      it "raises instance of submitted ArgumentError" do
        error = ArgumentError.new("error message")
        @double.should_receive(:something).and_raise(error)
        lambda {
          @double.something
        }.should raise_error(ArgumentError, "error message")
      end

      it "fails with helpful message if submitted Exception requires constructor arguments" do
        class ErrorWithNonZeroArgConstructor < RuntimeError
          def initialize(i_take_an_argument)
          end
        end

        @double.stub(:something).and_raise(ErrorWithNonZeroArgConstructor)
        lambda {
          @double.something
        }.should raise_error(ArgumentError, /^'and_raise' can only accept an Exception class if an instance/)
      end

--
module RSpec::Rails
  module Matchers
  end
end

begin
  require 'test/unit/assertionfailederror'
rescue LoadError
  module Test
    module Unit
      class AssertionFailedError < StandardError
      end
    end
  end
end

require 'rspec/rails/matchers/have_rendered'
require 'rspec/rails/matchers/redirect_to'
require 'rspec/rails/matchers/routing_matchers'
require 'rspec/rails/matchers/be_new_record'
require 'rspec/rails/matchers/be_a_new'
--
require 'active_support/core_ext'
require 'active_model'

module RSpec
  module Rails

    class IllegalDataAccessException < StandardError; end

    module Mocks

      module ActiveModelInstanceMethods
        # Stubs `persisted?` to return false and `id` to return nil
        # @return self
        def as_new_record
          self.stub(:persisted?) { false }
          self.stub(:id) { nil }
          self