byebugのガイドをおおざっぱになぞってみる(1)

GUIDE.mdに沿って実際に動かしてみて、自分で自分に説明してみた記録。翻訳ではない。(というか英語的にどうかって言われると自信がない。)

Byebugのバージョンは9.0.6、Rubyのバージョンは2.4.0p0。

GUIDE.mdの最初のサンプルコードについて、元のコードではデバッガの説明の入り口としては少し分かりにくいと思ったので以下の変更を加えている。

  # もともとのコード
  0.upto(n) {|i| tri += i }

  # 変更したコード
  0.upto(n) do |i|
    tri += i
  end

Introduction

First Steps

最初のサンプルとして以下の内容のtriangle.rbを作る。

#
# 三角数: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n
#
def triangle(n)
  tri = 0

  0.upto(n) do |i|
    tri += i
  end

  tri
end

t = triangle(5)
puts t

byebugコマンドで実行する。

$ byebug triangle.rb

[1, 10] in /private/tmp/triangle.rb
    1: #
    2: # 三角数: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n
    3: #
=>  4: def triangle(n)
    5:   tri = 0
    6: 
    7:   0.upto(n) do |i|
    8:     tri += i
    9:   end
   10: 
(byebug)

byebugコマンドは、最初に実行される行の前でプログラムをストップさせる。この例では4行目の前がその場所であり、=>によって次に実行される行を示している。

メソッド定義(def)の前でストップしたのは、Rubyが動的なプログラミング言語だからだ。

プログラムがストップすると、その場所の前後を含めた10行分を表示し、続いて命令待ちのプロンプトを表示する。(byebug)がそれだ。

byebugコマンドをpost-mortemモード(-mオプション)で実行していて、例外をキャッチしたときには、プロンプトが(byebug:post_mortem)になる。--no-quitオプション付きで実行していれば、プログラムが終了した後にも命令待ちとなり、(byebug:ctrl)というプロンプトを表示する。

(メモ: --no-stopオプションを指定すると最初の行でストップせず、そのまま実行を続ける。プログラム中でbyebugメソッドを呼び出すなど、明示的にストップする要因がなければそのまま終了する。)

プログラムを一行ずつ実行してみよう。

(byebug) step

[6, 15] in /private/tmp/triangle.rb
    6: 
    7:   0.upto(n) do |i|
    8:     tri += i
    9:   end
   10: 
   11:   tri
   12: end
   13: 
=> 14: t = triangle(5)
   15: puts t

step命令を使うと一行分だけプログラムを進められる。

この例では4行目のdefが実行され、次に実行される14行目の前でストップしている。

(byebug) # ここでリターンキーをたたく

[1, 10] in /private/tmp/triangle.rb
    1: #
    2: # 三角数: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n
    3: #
    4: def triangle(n)
=>  5:   tri = 0
    6: 
    7:   0.upto(n) do |i|
    8:     tri += i
    9:   end
   10: 

何も入力せず、リターンキーだけたたくと、直前の命令をもう一度繰り返すことができる。

ここではstep命令が繰り返され、14行目が実行される。そして次に実行される5行目の前でストップする。

(byebug) eval tri
nil

evalを使うと変数の値を表示できる。

eval triは変数triの値を表示するものだが、この時点ではtri = 0の前でストップしているため、その値はnilとなる。

(byebug) step

[2, 11] in /private/tmp/triangle.rb
    2: # 三角数: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n
    3: #
    4: def triangle(n)
    5:   tri = 0
    6: 
=>  7:   0.upto(n) do |i|
    8:     tri += i
    9:   end
   10: 
   11:   tri
(byebug) eval tri
0

さらにstep命令によりプログラムを進めると、tri = 0が実行される。再度evalを使うと今度は0が表示される。

ストップするたびにtriの値を表示して、どんな具合いに変化するかを見たければdisplay命令を使うとよい。

(byebug) display tri
1: tri = 0

step命令を使ってプログラムを進めてみよう。

(byebug) step
1: tri = 0

[3, 12] in /private/tmp/triangle.rb
    3: #
    4: def triangle(n)
    5:   tri = 0
    6: 
    7:   0.upto(n) do |i|
=>  8:     tri += i
    9:   end
   10: 
   11:   tri
   12: end

一行分進んでブロックの中身の前でストップ。この時点ではまだtriには何の変化もない。

さらにプログラムを進めてみる。

(byebug) 
1: tri = 0

[3, 12] in /private/tmp/triangle.rb
    3: #
    4: def triangle(n)
    5:   tri = 0
    6: 
    7:   0.upto(n) do |i|
=>  8:     tri += i
    9:   end
   10: 
   11:   tri
   12: end
(byebug) 
1: tri = 1

[3, 12] in /private/tmp/triangle.rb
    3: #
    4: def triangle(n)
    5:   tri = 0
    6: 
    7:   0.upto(n) do |i|
=>  8:     tri += i
    9:   end
   10: 
   11:   tri
   12: end

triの値が、ループ一回目の実行で0、二回目の実行で1、と変化しているのが分かる。

この後どのようにプログラムが進み、triがどう変化していくか一息に見てみたい。

具体的には、このループを抜けたところまでいちいち表示を止めず(プロンプトを出さず)実行させる。それにはfinish命令が使える。

メソッドやブロックはいくつかの呼び出し階層を作ることがある。finish命令を使うと、そうした階層を引数で指定した数だけ抜け出すまでプログラムを進めることができる。階層数を指定しなければ、現在の階層を抜ける。これは引数に1を指定したのと同じ動作になる。(0を指定すると、現在の階層を抜ける前まで実行が進む。)

現在、ストップしているのはトップ→triangle0.upto(n)→ブロック(ループの中身)と数えていくと四階層目になる。プログラムを進めたいのはuptoループを抜けたところまでだから、ブロックと、0.upto(n)の二階層分。finish 2と入力すればよい。

ただ一息にプログラムを進めると、途中の経過が分からなくなってしまう。そこでlinetraceオプションを設定する。

linetraceオプションが設定していると、プログラムをどこまで進めたかを示すために、実行中のファイル名と進んだ先の行が表示される。(=>と同様の表示) また、display命令に従った表示も行われる。ここではtriの値を表示させているため、プログラムの進行に応じたtriの変化を見て取れる。

ついでに表示を少しばかりシンプルにするためにbasenameオプションを指定して、ファイル名の表示からディレクトリを除くことにする。(指定しなければフルパスで表示される。)

(byebug) set linetrace
linetrace is on
(byebug) set basename
basename is on

それではプログラムを進めよう。

(byebug) finish 2
Tracing: triangle.rb:8     tri += i
1: tri = 3
Tracing: triangle.rb:8     tri += i
1: tri = 6
Tracing: triangle.rb:8     tri += i
1: tri = 10
Tracing: triangle.rb:11   tri
1: tri = 15
1: tri = 15

[6, 15] in /private/tmp/triangle.rb
    6:
    7:   0.upto(n) do |i|
    8:     tri += i
    9:   end
   10:
=> 11:   tri
   12: end
   13:
   14: t = triangle(5)
   15: puts t

0.upto(n)の実行を終え、次の11行目、triだけの行の直前でストップしている。

以上で最初のサンプルは終了。quit命令を使用してbyebugコマンドを終了させる。

(byebug) quit
Really quit? (y/n) y

quitを略してqでも同じく終了させられる。終了の確認(Really quit? (y/n))が必要なければ、命令に!を付けてquit!あるいはq!と入力すると確認なしで終了させられる。

(メモ: Byebug.tracingtrueの間、Byebug.commands.select {|cmd| cmd.always_run >= 2 }で得られる命令が進むごとに実行される。また、ストップするごとに同じくcmd.always_run >= 1の命令が実行される。対象になるのはByebug::DisplayCommandとByebug::ListCommandで、それぞれalways_runが2と1、display命令とlist命令に対応する。1: tri = 15の表示が重複しているのは、linetraceによるものと、ストップによるものの二回行われたため。なお、list命令は実行中のコード表示をする。)