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