先日、Python 向けのデバッグライブラリ IceCream が話題になっていました。
Python 以外にもいくつかの言語版があるようですが、残念ながら Ruby 版が無い。というわけで作ってしまいました。
はじめに
まず、このライブラリはいわゆる「print デバッグ」1の代替となるものです。
そもそも本格的にデバッグを行う場合、print デバッグではなく Byebug 等のデバッガを用いるべきです。
参考:printデバッグにさようなら!Ruby初心者のためのByebugチュートリアル
とはいえ print デバッグが非常に手軽なものであることは間違いなく、ちょっとしたデバッグにはいちいちデバッガを使うより print デバッグで済ませてしまうという方も多いでしょう。
しかし print デバッグは手軽な分そのままでは困ることも少なくありません。 print デバッグの手軽さはそのままに、もう少し便利にしてくれるライブラリが IceCream (そしてこの Ricecream )だと思います。
概要
print デバッグの困るところ
例えば、変数 foo
の値を調べたいとします。print デバッグの場合、以下のようなコードを追加することになります。
p foo
この場合、123
のような形で foo
の値が表示されます。
print デバッグをする箇所が 1 箇所であればこれで困りませんが、複数の変数をさまざまな場所で print デバッグしようとすると、先のコードでは出力されたのがどの変数なのか判りません。また、もともとさまざまな標準出力を行うプログラムの場合、print デバッグの出力値と本来の出力値が混ざってしまい見づらくなります。
以下のように書けば、そうした問題点を解決できます。
puts "[debug] foo: #{foo}" # => [debug] foo: 123
ですが、こんなのを入力するのは面倒です。2 これではせっかくの print デバッグの手軽さを台無しにしてしまいます。
Ricecream の場合
Ricecream を使えば、前者のような手軽な
ic foo
というコード 3 で、
ic| foo: 123
と表示することができるようになります。
変数等の表示
上の例では単に変数の値をそのまま表示させただけですが、ic
は任意の式を取ることができます。また、複数の引数も受け取れますし、引数が複数行にまたがっていても問題ありません。
require "ricecream"
ic foo * 2,
Integer.sqrt(foo)
# => ic| foo * 2: 246, Integer.sqrt(foo): 11
また、 ic
は引数をそのまま戻り値にするので、既存のコードに簡単に追加できます。例えば以下のコードにおいて foo.bar
の値を確認したいとしましょう。
return "result = #{foo.bar}"
puts
を使う場合、
puts foo.bar
return "result = #{foo.bar}"
としてしまうと foo.bar
が副作用を伴うようなメソッドだった場合、2 回呼び出されてしまうことで予期せぬ動作になる可能性があります。ですので
foobar = foo.bar
puts foobar
return "result = #{foobar}"
のように書くことになるでしょう。書くのも面倒ですし、デバッグ後の修正作業も面倒です。
一方、ic
を使えば
return "result = #{ic foo.bar}"
と簡単に書けます。
…とはいえ p
や pp
も同様に引数を戻り値にするので、この点に関してはあまり Ricecream の優位性はありません。
実行の流れを調べる
以下のようなメソッドが、foo
に到達しなかったとします。
def mymethod
return if cond1?
return if cond2?
return if cond3?
foo
end
このとき print デバッグでは以下のように様々な出力値の p
をあちこちに追加することで、どこで return したのかを調べることになるでしょう。例えば下の例で「1」「2」が出力されたのであれば、return if cond2?
のところで return したことが分かります。…しかし、出力値を一つ一つ変えて入力する手間がかかりますし、どこで出力された値なのかが直感的には判りません。
def mymethod
p 1
return if cond1?
p 2
return if cond2?
p 3
return if cond3?
p 4
foo
end
一方、以下のように引数無しで ic
を使えば
def mymethod
ic
return if cond1?
ic
return if cond2?
ic
return if cond3?
ic
foo
end
ic| script.rb:2 in mymethod at 23:59:52.822
ic| script.rb:4 in mymethod at 23:59:52.822
のように、どのスクリプトの何行目が実行されたのかが判りやすく表示されます。
限定されたスコープで ic を使う
Ricecream は Kernel を拡張してしまうので、使うのが不安だという方もいるかもしれません。
そんな方のために、Refinements 版もあります。
require "ricecream/refine"
using Ricecream
ic
また、using Ricecream::P
すれば p
をオーバーライドできます。
require "ricecream/refine"
using Ricecream::P
foo = 123
p foo # => ic| foo: 123
ic の表示を消す
print デバッグが終わった際に、うっかり追加した p
を消し忘れてしまうことってありませんか?特に普段は実行されない部分に追加したところとか。
もちろん Ricecream を使った場合も ic
を消し忘れる可能性はあります。ですが Ricecream.disable
を実行しておけば、万が一消し忘れたとしても出力がされなくなるので安心です。
require "ricecream"
ic # => 出力される
Ricecream.disable
ic # => 出力されない
Ricecream.enable
ic # => 出力される
その他いろいろ
prefix を変更
prefix (デフォルトでは ic|
)を変更できます。
Ricecream.prefix = "ricecream| "
メソッド Ricecream.prefix
を定義することで、より柔軟な変更ができます。
def Ricecream.prefix(location)
"ic #{Time.now}| "
end
出力先の変更
デフォルトでは出力先は STDERR ですが、これも変更できます。
Ricecream.output = STDOUT
def Ricecream.output(str)
@log ||= Logger.new("logfile.log")
@log.debug(str)
end
出力方法の変更
デフォルトでは Object#inspect
(つまり p
相当)を用いて値を出力しますが、Ricecream.arg_to_s
を定義することで変更できます。
def Ricecream.arg_to_s(arg)
PP.pp(arg, +"", 60)
end
呼び出し元情報を表示
Ricecream.include_context = true
すると、ic
を呼び出した場所の情報を付けて表示します。
ic foo # => ic| foo: 123
Ricecream.include_context = true
ic foo # => ic| script.rb:10 in <main>- foo: 123
色付け(実験的機能)
Ricecream.colorize = true
すると、色付きで表示します。ただし irb 1.2 以上が必要です。
対応できない場合
以下のような場合、ic
は適切な表示ができません。
同じ行に、引数の数が同じ ic
ic foo; ic bar
# => ic| foo: 123
# => ic| foo: 456
# ↑ bar なのに…
のように同じ行に引数の数が同じ ic があると、正しい表示ができません。
引数の数が異なれば問題ないので、無理やり引数を増やしてしまえば大丈夫です。
ic foo; ic bar, 1
# => ic| foo: 123
# => ic| bar: 456, 1
eval 中で ic
eval
中の ic
には対応できません。以下のように、とりあえず引数を <arg1>
のように表示します。
eval("ic foo") # => ic| (eval):1 in <main>- <arg1>: 123
引数展開
ic
に引数を展開して与えると対応できません。しかし、そもそもそんな行為に何の意味があるのでしょうか?
ic *[1, 2, 3] # => ic| script.rb:4 in <main>- <arg1>: 1, <arg2>: 2, <arg3>: 3
以上のように対応できない場合はありますが、レアケースだと思うので大抵の状況には対応できるはずです。
まとめ
デバッガの使い方を覚えようと思いつつ ついつい面倒で print デバッグしてしまう方は、この Ricecream を使ってみてはいかがでしょうか?