トップ 最新 追記 RSS feed

継続にっき

2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|
2014|05|09|
2015|12|
2017|09|

2013-02-09 (Sat)

))) Rubyの例外終了時に自動でREPLを起動する(2)

フィルタコマンドを作るときなどに Better Errors の様な感じで使えるREPLが欲しくなって、 ライブラリを作ってみたことがあります(Rubyの例外終了時に自動でREPLを起動する)。 *1

その時は、set_trace_funcのオーバーヘッドを避けるためにStandardError#initializeをCレベルで定義して その中でbindingを取得する*2という ややトリッキーな実装をしていたのですが、Ruby 2.0でset_trace_funcの高速版といえるTracePointが導入されたのでそちらを使って書き直してみました。

主な変更点は2点。

  • 例外発生直前のトレース情報を出力するようにした
    • メソッドチェインの途中の値の確認などが簡単に
  • Pure Rubyになった
    • Windowsでも楽に使えるように

gem installしてrequireするだけで利用できます(当然、1.9では動きません)。

$ gem install dexc

$ cat t.rb
def m(obj)
  obj.to_s + 1
end

m(0)

$ ruby -rdexc t.rb
 0:lib/dexc.rb:82>     tp.enable
   TracePoint#enable: false
 1:lib/dexc.rb:83>   end
   Dexc#start: false
 2:lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45>       return gem_original_require(path)
   Kernel#gem_original_require: true
 3:lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45>       return gem_original_require(path)
   Kernel#require: true
 4:t.rb:0>
   IO#set_encoding: #<File:t.rb (closed)>
 5:t.rb:0>
   IO#set_encoding: #<File:t.rb (closed)>
 6:t.rb:1> def m(obj)
   Module#method_added: nil
 7:t.rb:2>   obj.to_s + 1
   Fixnum#to_s: "0"
 8:t.rb:2>   obj.to_s + 1
   Exception#initialize: #<TypeError: no implicit conversion of Fixnum into String>
 9:t.rb:2>   obj.to_s + 1
   Class#new: #<TypeError: no implicit conversion of Fixnum into String>
10:t.rb:2>   obj.to_s + 1
   Exception#exception: #<TypeError: no implicit conversion of Fixnum into String>
11:t.rb:2>   obj.to_s + 1
   Exception#backtrace: nil

TypeError: no implicit conversion of Fixnum into String
        from t.rb:2:in `+'
        from t.rb:2:in `m'
        from t.rb:4:in `<main>'

From: t.rb @ line 2 Object#m:

    1: def m(obj)
 => 2:   obj.to_s + 1
    3: end

[1] pry(main)> obj
=> 0
[2] pry(main)> hist[7] # or dexc_hist[7]
=> "0"

例外が起きた環境でREPL(例ではPry)が起動されています。 また、`hist'を使うことで例外発生直前の一連のメソッドの返り値を取得できることが分かります。

なお、トレースが取れるようになって便利になった反面パフォーマンスは低下しました。 参考までに、Rubyに含まれているテスト(test/ruby)を走らせた時の所要時間を比較すると以下のようになります。

条件所要時間
dexcなし2:25.83
dexcあり(トレース無効(:raiseのみ))2:28.90
dexcあり(トレース有効)5:34.07

*1 実際には順序が逆で、実装方式を思いついたからライブラリを作ったというのが正しいのですが

*2 こうすると例外オブジェクト生成時に(Rubyレベルで)直近のbindingが取得できるので、大体のケースでうまく動く


2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|
2014|05|09|
2015|12|
2017|09|
トップ 最新 追記 RSS feed