2006年1月8日日曜日

rubyで自分の例外クラスを作る方法

C++とかJavaに慣れているひとは、つい

class MyException < Exception

と、Exceptionクラスを継承しがち。けれど、Exceptionクラスの配下には、
fatalとか、NoMemoryErrorなど、致命的なものと、回復可能かもしれないもの(StandardError)が
きちんと分けられているのです。

従って、自分のコード中で処理したい例外は、

class MyException < StandardError

と書くのが良い。その根拠として、rubyのrescue文は、

begin
(例外が発生)
rescue
(StandardError、またはその派生クラスがここで補足される)
end

と、補足する例外クラスを指定しないと、デフォルトでStandardErrorを捕まえるように設計されています。

このせいで、rubyunitで補足できない例外があって長時間苦しみました。
sqlite3-rubyはExceptionから派生したクラスで例外を出すので、もう大変。

rubyでは、rescueで補足されなかった例外があると、

leo@celia:~/../ruby/depot> rake test_units
/usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/resultset.rb:76:in `check': cannot rollback - no transaction is active (SQLite3::SQLException)
from /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/resultset.rb:68:in `commence'
from /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/resultset.rb:61:in `initialize'
from /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/statement.rb:158:in `execute'
from /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/database.rb:211:in `execute'
from /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/database.rb:186:in `prepare'
from /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/database.rb:210:in `execute'
from /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.1.0/lib/sqlite3/database.rb:620:in `rollback'
from /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/active_record/connection_adapters/sqlite_adapter.rb:179:in `rollback_db_transaction'
... 10 levels...
from /usr/local/lib/ruby/1.8/test/unit/autorunner.rb:200:in `run'
from /usr/local/lib/ruby/1.8/test/unit/autorunner.rb:13:in `run'
from /usr/local/lib/ruby/1.8/test/unit.rb:285
from /usr/local/lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake/rake_test_loader.rb:5


というように、途中が省略されて内容が表示されるのです。さらに、C++やJavaの影響でrescue文ですべての例外が補足できると思いこんでいたために、デバッグが困難を極めました。例外をrescueで捕まえることができれば、Exception.backtraceを実行して完全表示できるのですが。

(思考の過程)
  1. raise 文を、コードの動作を確認しながら、ruby on railsやsqlite3-rubyのソースコードのあちこちに埋め込んで、backtraceを表示させようと試みるが、なぜかうまくいかない。ここで数時間。
  2. どんな状況でこの例外表示が省略されるのかを、テストコードを書いて調べてみた
  3. 普通に例外をrescueしてputs $@とするだけで、full traceが表示される。。。
  4. どうやら、rescueしないで、スクリプトを抜ける場合に省略表示が入る模様。
  5. それでは、この省略表示をやめる方法があると考え、小1時間調べる。
  6. 結局、そんな設定はなく、rubyのソースコードに定数値で書かれていて不変であることが判明
  7. rubyに手をいれるのは怖いので、rubyunitの動作をもう一度確認。raise文もあるし、補足できないような例外があるとは思えない。
  8. rubyのバグかと思い調べるが、そんなことはなさそうだった
  9. 基本に戻って、Programming Ruby 2nd Editionを読んでみる。
  10. rescueはStandardErrorをデフォルトで捕らえると書いてある。。。
  11. 自分で作成する例外クラスは、StandardErrorから派生すること、と書いてある…
  12. sqlite3-rubyのソースコードの例外定義部分を見てみる
  13. sqlite3-rubyはすべての例外クラスをExceptionから派生していた…
  14. この部分を、StandardErrorに変更
  15. RubyUnitが無事に例外を補足。エラーのフルトレースが表示される。
基本は大事。rubyの本、最初に通して読んでおくべきだった。
まだ、ruby歴半月なのです。

0 件のコメント:

License

Creative Commons LicenseLeo's Chronicle by Taro L. Saito is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.1 Japan License.