Slide 1

Slide 1 text

Ruby との対話 pry を使い pry をデバッグし pry のバグを直す話 2018.11.29 スタートアップテック vol.1 @zaru

Slide 2

Slide 2 text

@zaru 株式会社ベーシック CTO

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

https://form.run

Slide 5

Slide 5 text

Ruby との対話

Slide 6

Slide 6 text

REPL Read Eval Print Loop

Slide 7

Slide 7 text

Ruby の REPL といえば pry 今日は pry の edit について話します

Slide 8

Slide 8 text

pry の edit を使っている人

Slide 9

Slide 9 text

edit の使い方 $ pry [1] pry(main)> 100 * 10 => 1000 [2] pry(main)> edit edit と打つと直前に入力した内容をエディタで開いて編集できる。エ ディタの指定は $EDITOR もしくは Pry.config.editor で指定可能。

Slide 10

Slide 10 text

edit が便利だから紹介したい

Slide 11

Slide 11 text

コードを修正したい時 たとえば 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

Slide 12

Slide 12 text

例えば ! を使う ! で入力バッファをクリアできる。すべてを無にしたい性格の人向け。 [10] pry(main)> class Mu [10] pry(main)* def kyomu [10] pry(main)* ! Input buffer cleared! 入力中のものを修正したい場合は amend-line か ! を使う。

Slide 13

Slide 13 text

例えば 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 で編集したものはファイ ルに保存され、その場でリロードされる。便利。

Slide 14

Slide 14 text

edit のオプション いくつかあるが、これだけ覚えておけば大丈夫。 edit : 直前に INPUT したものを編集 edit sample.rb : 指定ファイルを編集 edit MyClass#my_method : 指定クラスの指定メソッドを編集 edit --temp : 新規に入力 edit --in n : 指定番目に INPUT したものを編集

Slide 15

Slide 15 text

ここまでが pry edit の紹介

Slide 16

Slide 16 text

ここから pry のバグを見つけて pry を使い pry をデバッグし pry のバグを直す話

Slide 17

Slide 17 text

発端 何回もコードを実行して、ふと前に定義したメソッドを編集したくなっ た時。 [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 は空っぽ…

Slide 18

Slide 18 text

そこで 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 というオブジェクトから取得しているっぽい

Slide 19

Slide 19 text

Pry::Ring クラスの @buffer に配列で入力コマンドが格納されている。 @max_size という変数もあるので記録する最大値が決まっていて、その 値を超えたから、さっきの edit --in 2 を指定しても空になったと推 測。 [5] pry(main)> _pry_.input_ring => #>

Slide 20

Slide 20 text

Pry::Ring の実装を確認する [6] pry(main)> edit Pry::Ring スレッドセーフ 上限値の決まっている配列クラス 上限値を超えたら古いものから上書きしていく << [] to_a clear などのメソッド

Slide 21

Slide 21 text

手軽にデバッグできるように @max_size を少なくする。 .pryrc で指定 Pry.config.memory_size = 3

Slide 22

Slide 22 text

上限が決まっていて消えるのは仕方がない。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"]

Slide 23

Slide 23 text

消えた?!

Slide 24

Slide 24 text

[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"] が返ってきて欲しい。

Slide 25

Slide 25 text

オブジェクの監視には watch _pry_.input_ring の変化を知りたいので watch コマンドを使う。 [1] pry(main)> watch _pry_.input_ring Watching _pry_.input_ring watch: _pry_.input_ring => #> watch で指定したオブジェクトに変更があったら、それを自動で表示し てくれる便利なやつ。

Slide 26

Slide 26 text

次に 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

Slide 27

Slide 27 text

やりたい事は入力した順番通りに @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

Slide 28

Slide 28 text

[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"] めでたい

Slide 29

Slide 29 text

あとはこの配列のインデックスを使えば 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 になっているはず。

Slide 30

Slide 30 text

次に [] の実装を確認する。 [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

Slide 31

Slide 31 text

なぜか並び替えのロジックが 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

Slide 32

Slide 32 text

edit で修正したものはファイルに反映されているので、そのまま git add & git commit すれば プルリクの作成ができて作業完了。 もしファイルに修正内容を反映したくない場合は edit -p オプションを 使えばメモリ上だけの反映になる。

Slide 33

Slide 33 text

というわけで、マージされたプルリクはこちら https://github.com/pry/pry/pull/1898

Slide 34

Slide 34 text

まとめ デバッグには edit が便利 監視には watch が便利 $ cd ls 辺りは普通に便利 edit --in をもっと活用するためには、もうひと工夫必要 もし時間があれば紹介

Slide 35

Slide 35 text

おまけ

Slide 36

Slide 36 text

edit --in に使う番号がわかりにくいという課題

Slide 37

Slide 37 text

hist コマンドで表現できれば良い… ?

Slide 38

Slide 38 text

pry は入力履歴をどうやって管理しているのか

Slide 39

Slide 39 text

入力内容の記録は 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

Slide 40

Slide 40 text

_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 => #>

Slide 41

Slide 41 text

雑に 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

Slide 42

Slide 42 text

[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 幸せ

Slide 43

Slide 43 text

exit