Share
Embed
Ruby との対話 : pry を使い pry をデバッグし pry のバグを直す話
zaru
November 29, 2018
Transcript
43 slides
-
edit の使い方 $ pry [1] pry(main)> 100 * 10 =>
1000 [2] pry(main)> edit edit と打つと直前に入力した内容をエディタで開いて編集できる。エ ディタの指定は $EDITOR もしくは Pry.config.editor で指定可能。 -
コードを修正したい時 たとえば pry でコードをいくつか書きながらトライアンドエラーをする 場合、毎回似たようなコードを入力するのがダルい。コードをミスった ときに修正するのもダルい。矩形選択もだるい。そもそもキーボードか ら手を離したくない。 例えば amend-line を使う
入力中のものであれば amend-line n..m で指定行数を編集可能。 [6] pry(main)> class Hoge [6] pry(main)* def piyo [6] pry(main)* p 'piyo' [6] pry(main)* end [6] pry(main)* amend-line 2..4 -
例えば ! を使う ! で入力バッファをクリアできる。すべてを無にしたい性格の人向け。 [10] pry(main)> class Mu [10]
pry(main)* def kyomu [10] pry(main)* ! Input buffer cleared! 入力中のものを修正したい場合は amend-line か ! を使う。 -
例えば edit --in を使う edit --in は過去に入力したものを指定して編集可能。 [2] pry(main)> class
RubyLT [2] pry(main)* def start [2] pry(main)* end [2] pry(main)* end => :start [3] pry(main)> edit --in 2 これで class RubyLT 自体の編集が可能。edit で編集したものはファイ ルに保存され、その場でリロードされる。便利。 -
edit のオプション いくつかあるが、これだけ覚えておけば大丈夫。 edit : 直前に INPUT したものを編集 edit sample.rb
: 指定ファイルを編集 edit MyClass#my_method : 指定クラスの指定メソッドを編集 edit --temp : 新規に入力 edit --in n : 指定番目に INPUT したものを編集 -
発端 何回もコードを実行して、ふと前に定義したメソッドを編集したくなっ た時。 [2] pry(main)> class RubyLT [2] pry(main)* def
start [2] pry(main)* end [2] pry(main)* end . . . [113] pry(main)> [114] pry(main)> edit --in 2 2 番目を指定するも vim は空っぽ… -
そこで edit --in オプションの仕様を知るためにコードを確認する [2] pry(main)> edit edit def input_expression
case opts[:i] when Range (_pry_.input_ring[opts[:i]] || []).join when Integer _pry_.input_ring[opts[:i]] || "" else raise Pry::CommandError, "Not a valid range: #{opts[:i]}" end end _pry_.input_ring というオブジェクトから取得しているっぽい -
Pry::Ring クラスの @buffer に配列で入力コマンドが格納されている。 @max_size という変数もあるので記録する最大値が決まっていて、その 値を超えたから、さっきの edit --in 2
を指定しても空になったと推 測。 [5] pry(main)> _pry_.input_ring => #<Pry::Ring:0x007feb2e07ba68 @buffer= [nil, "class Hoge\n def piyo\n end\nend\n", "Hoge.new.piyo\n", "_pry_.input_ring\n @count=6, @max_size=100, @mutex=#<Thread::Mutex:0x007feb2e07ba40>> -
上限が決まっていて消えるのは仕方がない。memory_size を上げれば 済む話。ただし上書きされていくと、入力する番号が判断しにくいので @buffer 配列にインデックス番号をつけて出力するコマンドを実装しよ うと挙動を確認。 [1] pry(main)> 1 =>
1 [2] pry(main)> 1 => 1 [3] pry(main)> 1 => 1 [4] pry(main)> 1 => 1 [5] pry(main)> _pry_.input_ring.to_a => ["1\n"] -
[1] pry(main)> 1 => 1 [2] pry(main)> 1 => 1
[3] pry(main)> 1 => 1 [4] pry(main)> 1 => 1 [5] pry(main)> _pry_.input_ring.to_a => ["1\n"] 1 を4 回入力しているので @max_size が 3 の場合 ["1\n", "1\n", "1\n"] が返ってきて欲しい。 -
オブジェクの監視には watch _pry_.input_ring の変化を知りたいので watch コマンドを使う。 [1] pry(main)> watch _pry_.input_ring
Watching _pry_.input_ring watch: _pry_.input_ring => #<Pry::Ring:0x007fde395ab468 @buffer=[nil, ""], @count=2, @max_size=3, @mutex=#<Thread::Mutex:0x007fde395ab440>> watch で指定したオブジェクトに変更があったら、それを自動で表示し てくれる便利なやつ。 -
次に Pry::Ring#to_a の実装を確認する。 last_part + (@buffer - last_part) があやしい (*)
`$` コマンドは `show-source` のエイリアス。指定クラスやオブジェクトのソースが見れる [8] pry(main)> $ Pry::Ring#to_a From: /Users/hiro/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/pry-0.12.2/lib/pry/ri Owner: Pry::Ring Visibility: public Number of lines: 6 def to_a return @buffer.dup if count <= max_size last_part = @buffer.slice(count % max_size, @buffer.size) last_part + (@buffer - last_part) end -
やりたい事は入力した順番通りに @buffer を並び替えたいだけ。一番最 初の入力したところから、2 つの配列に分けて前後を入れ替えて結合す るという感じ。でも配列同士の引き算は同一の要素を全部除く仕様。 [1, 1, 1] -
[1] => [] コードをその場で修正してデバッグしてみる。 [8] pry(main)> edit Pry::Ring#to_a last_part = @buffer.slice(count % max_size, @buffer.size) last_part.concat @buffer.slice(0, count % max_size) これで OK -
[16] pry(main)> 1 => 1 [17] pry(main)> 1 => 1
[18] pry(main)> 1 => 1 [19] pry(main)> 1 => 1 [20] pry(main)> _pry_.input_ring.to_a => ["1\n", "1\n", "1\n"] めでたい -
あとはこの配列のインデックスを使えば edit --in に利用できるはず。 [1] pry(main)> 1 => 1 [2]
pry(main)> 2 => 2 [3] pry(main)> 3 => 3 [4] pry(main)> 4 => 4 [5] pry(main)> edit --in 0 ここでは最初に入力した 1 は消えているので 0 番目のインデックス指 定は先頭の 2 になっているはず。 -
次に [] の実装を確認する。 [6] pry(main)> $ Pry::Ring#[] From: /Users/hiro/Sites/pry/lib/pry/ring.rb @
line 55: Owner: Pry::Ring Visibility: public Number of lines: 11 def [](index) @mutex.synchronize do return @buffer[(count + index) % max_size] if index.is_a?(Integer) return @buffer[index] if count <= max_size # Swap parts of array when the array turns page and starts overwriting # from the beginning, then apply the range. last_part = @buffer.slice([index.end, max_size - 1].min, count % max_size) (last_part + (@buffer - last_part))[index] end end -
なぜか並び替えのロジックが to_a と違う。このロジックだと場合よっ てはずれてしまうので単純に先程直した to_a を呼んでインデックスア クセスするように修正する。 def [](index) @mutex.synchronize
do return @buffer[(count + index) % max_size] if index.is_a?(Integer) return @buffer[index] if count <= max_size to_a[index] end end これで OK -
edit で修正したものはファイルに反映されているので、そのまま git add & git commit すれば プルリクの作成ができて作業完了。 もしファイルに修正内容を反映したくない場合は
edit -p オプションを 使えばメモリ上だけの反映になる。 -
まとめ デバッグには edit が便利 監視には watch が便利 $ cd ls
辺りは普通に便利 edit --in をもっと活用するためには、もうひと工夫必要 もし時間があれば紹介 -
入力内容の記録は hist と input_ring の2種類 hist コマンドは入力改行ごとに番号が入る。つまり edit --in には使え
ない番号。 [124] pry(main)> class Hoge [124] pry(main)* def piyo [124] pry(main)* end [124] pry(main)* end => :piyo [125] pry(main)> hist 1: hist 2: class Hoge 3: def piyo 4: end 5: end -
_pry_.input_ring というオブジェクトは、1 回の入力が1 つの値になって いる。つまり複数行でも1 つ。 この @buffer を hist
のように表示すれば OK [1] pry(main)> class Hoge [1] pry(main)* def piyo [1] pry(main)* end [1] pry(main)* end => :piyo [2] pry(main)> Hoge.new.piyo => nil [3] pry(main)> _pry_.input_ring => #<Pry::Ring:0x007feb2e07ba68 @buffer=[nil, "class Hoge\n def piyo\n end\nend\n", "Hoge.new.piyo\n", "_pry_.input @count=4, @max_size=100, @mutex=#<Thread::Mutex:0x007feb2e07ba40>> -
雑に hist コマンドを編集する。 [1] pry(main)> edit hist Pry::Code が番号表示用のクラスなので、そこに input_ring
配列をつっ こむだけ。 opt.on :i, :'input-numbers', "Show input numbers" @history = if opts.present?(:'input-numbers') Pry::Code.new(_pry_.input_ring.to_a.slice(1..-1), 0) else find_history end -
[16] pry(main)> def hoge [16] pry(main)* 'hoge' [16] pry(main)* end
=> :hoge [17] pry(main)> 100 * 10 => 1000 [18] pry(main)> hist -i 0: def hoge 'hoge' end 1: 100 * 10 [19] pry(main)> edit --in 0 幸せ