Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby との対話 : pry を使い pry をデバッグし pry のバグを直す話

zaru
November 29, 2018

Ruby との対話 : pry を使い pry をデバッグし pry のバグを直す話

zaru

November 29, 2018
Tweet

More Decks by zaru

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

  3. View Slide

  4. https://form.run

    View Slide

  5. Ruby
    との対話

    View Slide

  6. REPL
    Read Eval Print Loop

    View Slide

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

    View Slide

  8. pry
    の edit
    を使っている人

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. ここまでが pry edit
    の紹介

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. 消えた?!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  35. おまけ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  40. _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
    => #@buffer=[nil, "class Hoge\n def piyo\n end\nend\n", "Hoge.new.piyo\n", "_pry_.input
    @count=4,
    @max_size=100,
    @mutex=#>

    View Slide

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

    View Slide

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

    View Slide

  43. exit

    View Slide