はじめに
この記事はByebug(バイバグ)というgemを使ったデバッグ方法を説明するチュートリアル記事です。
JavaやC#のようなコンパイル型の言語ではEclipseやVisual StudioのようなIDEを使って開発することが主流です。
なので、自然とIDEに標準装備されているデバッガを使ってステップ実行したりすることが多いと思います。
一方、RubyではRubyMineのような有料IDEもあるものの、IDEではなくテキストエディタを使って開発している人の方がまだまだ多いと思います。
そうすると、初心者の方はなんとなく「Rubyでデバッガを使ってデバッグするのは無理なのでは?」と考えてしまう人も多いかもしれません。(僕は初心者の頃そう思ってました・・・。)
ですが、そんなことはありません!
RubyでもIDEを使わずにターミナル上でデバッガを使ってデバッグすることは可能です。
というわけで、この記事ではRubyでデバッガを使う方法をチュートリアル形式で説明します。
対象となるRubyとByebugのバージョン
この記事では以下の環境(2017年1月16日時点での最新環境)で動作確認しています。
- Ruby: 2.4.0
- Byebug: 9.0.6
- OS: macOS Sierra 10.12.2
備考:Windows環境での動作について
コメント欄にて「Windows環境ではrestart
などの一部機能が正しく動作しない」という報告をもらっています。
筆者はWindowsでは動作確認していないため、Windowsユーザーの方はOSやRuby、Byebugのバージョン情報とともに「これは動いた」「これは動かなかった」等のコメントをもらえると助かります。
ただし、「ByebugはWindows環境(RubyInstaller版のRuby)をサポートしている」というのがByebug作者の見解ですので(参考)、もしうまく動かない点があればByebugの公式リポジトリにてissueを投稿してください。
(2022.9.10追記)Byebugよりdebug.gemの方がオススメ?
Ruby 3.1からはRuby標準のdebugライブラリ(debug.gem、またはruby/debugと呼ばれる)がかなり高機能に進化しました。
基本的な使い方はByebugと大きく変わらないので、この記事でByebugの使い方を学び、そのあとdebug.gemを使ってみるのもオススメです。
以下の記事ではByebugとdebug.gemのコマンド対応表が載っています。
また、拙著「プロを目指す人のためのRuby入門 改訂2版」でもdebug.gemの使い方を説明していますので、こちらも参考にしてみてください。
printデバッグとデバッガは何が違うの?
チュートリアルを開始する前にprintデバッグとデバッガを使ったデバッグの違いを簡単に説明しておきます。
まず、printデバッグはprint文(ただしRubyの場合はputs
やp
が主流なはず)をコード内に埋め込んで、変数の値や実行された条件分岐を確認するデバッグ手法のことです。
例を挙げると以下のようなコードになります。
(puts
はいずれもprintデバッグ目的で使っています)
def fizz_buzz(n)
puts n
if n % 15 == 0
puts '15'
'Fizz Buzz'
elsif n % 3 == 0
puts '3'
'Fizz'
elsif n % 5 == 0
puts '5'
'Buzz'
else
puts 'other'
n.to_s
end
end
printデバッグは手軽な方法ですし、この方法でさっと問題が解決するのであればそれで構いません。
ですが、複雑なコードや難しいバグに遭遇すると、どのポイントにどんなprint文を埋め込めばいいのか事前に見当が付かないため、正解にたどり着くまで何度も試行錯誤することになります。
しかし、そんな作業を繰り返すのは非常に非効率です。
そんなときこそデバッガの出番です。
デバッガを使えば、任意のポイントでプログラムを停止させ、変数の値やメソッドの戻り値を対話的に確認することができます。
デバッガはその場その場で確認したいコードや値を柔軟に変更できるため、printデバッグよりも効率良くデバッグすることが可能です。
もしあなたが「Rubyのデバッガを使ったことがなく、毎回printデバッグしかやっていない」というのであれば、このチュートリアルにトライしてデバッガを使いこなせるようになるべきです!
Byebugを使ったデバッグのデモ
Byebugを使ったデバッグの雰囲気を確認してもらうためにアニメーションGIFを作成しました。
Byebugを使うとこんな感じでデバッグできます。
もちろん、Byebugの使い方はこのあとで詳しく説明するので、今はよくわからなくても問題ありません。
なぜByebugなの?
Rubyには標準のdebugライブラリも存在しています。
しかし、Twitter上でアンケートを採ったところ、標準のdebugライブラリよりもByebugを使っている人の方が多数派でした。
Ruby/Railsプログラマのみなさんにアンケートです。RubyやRailsでデバッグするときは、どのデバッガを使っていますか?(最もよく使うものを選択)
— Junichi Ito (伊藤淳一) (@jnchito) January 12, 2017
選択肢になければリプライで回答をお願いします~。
なので、この記事ではRubyプログラマの中で最もポピュラーなByebugを説明の対象としています。
おことわり
実は僕は(こんな記事を書いておきながら)普段ほとんどByebugを使っていません。
僕がデバッグでよく使っているのはRubyMineです。
Byebugをよく使っている人がこの記事を読んで、明らかに間違っている点を見つけたり、「これは絶対説明しておくべき!」という便利機能を知っていたりしたら、編集リクエストやコメント欄等で優しく指摘してやってください。
それではここからチュートリアルのスタートです!
このチュートリアルで使用するサンプルコードを準備する
このチュートリアルでは以下の2つのファイルを使います。
1つ目はFizz Buzz問題を解く、プログラム本体です。
def fizz_buzz(n)
if n % 15 == 0
'Fizz Buzz'
elsif n % 3 == 0
'Fizz'
elsif n % 5 == 0
'Buzz'
else
n.to_s
end
end
もう一つは上のfizz_buzz
メソッドをテストするMinitestのコードです。
require './lib/fizz_buzz'
require 'minitest/autorun'
class FizzBuzzTest < Minitest::Test
def test_fizz_buzz
assert_equal '1', fizz_buzz(1)
assert_equal '2', fizz_buzz(2)
assert_equal 'Fizz', fizz_buzz(3)
assert_equal '4', fizz_buzz(4)
assert_equal 'Buzz', fizz_buzz(5)
assert_equal 'Fizz', fizz_buzz(6)
assert_equal 'Fizz Buzz', fizz_buzz(15)
end
end
これら2つのファイルをそれぞれ次のような配置になるように保存してください。
.
├── lib
│ └── fizz_buzz.rb
└── test
└── fizz_buzz_test.rb
サンプルコードを実行してみる
ファイルを保存したら、次のようにしてテストコードを実行してください。
(これはByebugを使わない普通のプログラム実行です)
$ ruby ./test/fizz_buzz_test.rb
Run options: --seed 52560
# Running:
.
Finished in 0.000715s, 1397.7062 runs/s, 33544.9483 assertions/s.
1 runs, 7 assertions, 0 failures, 0 errors, 0 skips
上記のような実行結果になればサンプルコードのセットアップは完了です。
Byebugをインストールする
次に、Byebug gemをインストールしましょう。
gem install byebug
というコマンドを実行すると、ByebugがPCにインストールされます。
(インストール時はPCがインターネットに接続している必要があります。)
$ gem install byebug
Building native extensions. This could take a while...
Successfully installed byebug-9.0.6
1 gem installed
Byebugがちゃんとインストールできていれば、次のようにしてバージョンを確認することができます。
$ byebug -v
Running byebug 9.0.6
さあ、これでByebugのインストールも完了しました!
ここからいよいよByebugを使ったデバッグ方法を説明していきます。
みなさんも手元のPCで同じように操作してみてください。
Byebugからプログラムを起動する
Byebugからプログラムを起動するには、ruby
コマンドの代わりにbyebug
コマンドを使います。
$ byebug ./test/fizz_buzz_test.rb
すると実行したプログラムの最初のステップでプログラムが停止します。
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
=> 1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
=>
は現在停止している行を表しています。
また最後の(byebug)
はプロンプトになっていて、何かしらの入力を待機しています。
continueで処理を続行する
では、この状態でcontinue
と入力してみましょう。
(byebug) continue
Run options: --seed 51632
# Running:
.
Finished in 0.000665s, 1502.7110 runs/s, 10518.9767 assertions/s.
1 runs, 7 assertions, 0 failures, 0 errors, 0 skips
そうするとプログラムが再開し、最後まで実行されます。
ただし、ブレイクポイント(後述)が設定されていた場合は、次のブレイクポイントで停止します。
helpでコマンドの説明と短縮形を確認する
もういちど同じようにByebugからプログラムを実行してください。
$ byebug ./test/fizz_buzz_test.rb
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
=> 1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
今度はhelp continue
と入力します。
(byebug) help continue
c[ont[inue]][ <line_number>]
Runs until program ends, hits a breakpoint or reaches a line
(byebug)
ご覧のとおり、continue
コマンドの説明が表示されました。
c[ont[inue]]
とあるので、continue
コマンドはcont
やc
と短縮できるようです。
また引数に数字を渡すと、その行で処理が止まるようですね。
試しにcont 6
と入力してみましょう。
(byebug) cont 6
Run options: --seed 59792
# Running:
Stopped by breakpoint 1 at /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb:6
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
=> 6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
そうすると、確かに6行目でプログラムが停止しました。
今度はc
と入力してください。
(byebug) c
.
Finished in 143.663259s, 0.0070 runs/s, 0.0487 assertions/s.
1 runs, 7 assertions, 0 failures, 0 errors, 0 skips
今回は行番号を指定していないので最後まで処理が実行されました。
このように、Byebugのコマンドには短縮形が用意されていることが多いので、コマンドをタイプするのが面倒なときはhelp
コマンドで短縮形を確認すると良いでしょう。
stepとfinishでステップイン・ステップアウトする
さて、再度Byebugからプログラムを起動します。
$ byebug ./test/fizz_buzz_test.rb
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
=> 1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
continue
コマンドだと最後まで実行されてしまうので、今回は1行ずつステップ実行することにします。
ステップ実行する場合はstep
コマンドを使います。
step
と入力してみましょう。
(byebug) step
[35, 44] in /Users/jit/.rbenv/versions/2.4.0-rc1/lib/ruby/2.4.0/rubygems/core_ext/kernel_require.rb
35: #
36: # The normal <tt>require</tt> functionality of returning false if
37: # that file has already been loaded is preserved.
38:
39: def require path
=> 40: RUBYGEMS_ACTIVATION_MONITOR.enter
41:
42: path = path.to_path if path.respond_to? :to_path
43:
44: spec = Gem.find_unresolved_default_spec(path)
(byebug)
お、なんか見覚えのないコードが出てきましたね?
これはrequire './lib/fizz_buzz'
のrequire
メソッド内で実行が止まっているためです。(ステップイン)
さらにstep
と入力すると次のようになります。
(byebug) step
[181, 190] in /Users/jit/.rbenv/versions/2.4.0-rc1/lib/ruby/2.4.0/monitor.rb
181:
182: #
183: # Enters exclusive section.
184: #
185: def mon_enter
=> 186: if @mon_owner != Thread.current
187: @mon_mutex.lock
188: @mon_owner = Thread.current
189: @mon_count = 0
190: end
(byebug)
どんどんライブラリのコードに入っていきますね。
しかしこの調子でstep
を入力し続けると、なかなか処理が前に進みません。
そこでfinish
コマンドを使います。
finish
コマンドを使うと、実行中のスタックを全部実行し終わった直後でプログラムが停止します。(ステップアウト)
たとえば今の状態であれば、finish
コマンドを2回打ち込むと、元のfizz_buzz_test.rb
のコードに戻ってこられます。
(byebug) finish
[37, 46] in /Users/jit/.rbenv/versions/2.4.0-rc1/lib/ruby/2.4.0/rubygems/core_ext/kernel_require.rb
37: # that file has already been loaded is preserved.
38:
39: def require path
40: RUBYGEMS_ACTIVATION_MONITOR.enter
41:
=> 42: path = path.to_path if path.respond_to? :to_path
43:
44: spec = Gem.find_unresolved_default_spec(path)
45: if spec
46: Gem.remove_unresolved_default_spec(spec)
(byebug) finish
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
1: require './lib/fizz_buzz'
=> 2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
ご覧のとおり、fizz_buzz_test.rb
のコードに戻ってきました。
ちなみにstep
の短縮形はs
、finish
の短縮形はfin
です。
(byebug) help step
s[tep][ times]
Steps into blocks or methods one or more times
(byebug) help finish
fin[ish][ n_frames]
Runs the program until frame returns
If no number is given, we run until the current frame returns. If a
number of frames `n_frames` is given, then we run until `n_frames`
return from the current position.
(byebug)
また、step 2
やfinish 2
のように数字を指定すると「step
を2回」や「finish
を2回」実行したことと同じになります。
restartでプログラムを最初からやり直す
このチュートリアル通りに進めていれば、今はfizz_buzz_test.rb
の2行目で停止しているはずです。
次の説明ではまた最初からやり直したいのですが、プログラムを最後まで実行しなくてもrestart
コマンドを使うと最初からやり直すことができます。
というわけで、restart
と入力してください。
(byebug) restart
Re exec'ing:
/Users/jit/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/byebug-9.0.6/bin/byebug /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
=> 1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
ご覧のとおり、また1行目に戻ってきていますね。
nextでステップオーバー、リターンキーで直前のコマンドを繰り返す
さて、今度はnext
コマンドを使ってみましょう。
next
コマンドはstep
コマンドに似ていますが、step
とは異なり途中でメソッドがあってもメソッドの中に入らず、次の行に進みます。(ステップオーバー)
ではnext
と入力してみましょう。
(byebug) next
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
1: require './lib/fizz_buzz'
=> 2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
step
のときと違って、require
メソッドの中に入らず、次の行に進みましたね。
さらに進めたいときはもう一度next
を・・・としてもいいのですが、Byebugは何も入力せずにリターンキーを押すと、直前のコマンドを繰り返すことができます。
というわけで、そのままリターンキーを押してください。
(下のサンプルではリターンキーの入力を<RETURN>
と表示しています)
(byebug) <RETURN>
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
=> 4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
すると、2行目もステップオーバーして4行目でプログラムが停止します。
ちなみに、next
の省略形はn
です。
step
と同じようにnext 2
のようにすると、「next
コマンド2回」の意味になります。
(byebug) help next
n[ext][ nnn]
Runs one or more lines of code
breakでブレークポイントを追加する
Byebugでプログラムの実行を任意のポイントで停止させるためにはbreak
コマンドでブレークポイントを付けます。
ブレークポイントを付けると、continue
コマンドでプログラムの実行を再開したときもそこでプログラムが停止します。
実際に使ってみましょう。
このチュートリアルを順に進めている場合は、fizz_buzz_test.rb
の4行目で停止しているはずです。
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
=> 4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
ここでbreak 6
と入力します。
(byebug) break 6
Successfully created breakpoint with id 1
これで6行目にブレークポイントが設定されました。
continue
コマンドでプログラムを再開すると、6行目で停止するはずです。
(byebug) continue
Run options: --seed 58011
# Running:
Stopped by breakpoint 1 at /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb:6
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
=> 6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug)
ちゃんと6行目で止まりましたね。
break
コマンドでは他のファイルに対してもブレークポイントを付けることができます。
試しにlib/fizz_buzz.rb
の2行目にブレークポイントを付けてみましょう。
break ./lib/fizz_buzz.rb:2
と入力し、それからcontinue
を入力してください。
(byebug) break ./lib/fizz_buzz.rb:2
Successfully created breakpoint with id 2
(byebug) continue
Stopped by breakpoint 2 at /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb:2
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb
1: def fizz_buzz(n)
=> 2: if n % 15 == 0
3: 'Fizz Buzz'
4: elsif n % 3 == 0
5: 'Fizz'
6: elsif n % 5 == 0
7: 'Buzz'
8: else
9: n.to_s
10: end
(byebug)
ご覧のとおり、fizz_buzz.rb
の2行目で停止しました。
ブレークポイントを管理する
info breakpoints
と入力すると、現在設定されているブレークポイントが一覧表示されます。
(byebug) info breakpoints
Num Enb What
1 y at /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb:6
breakpoint already hit 1 time
2 y at /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb:2
breakpoint already hit 1 time
一番左の数字はブレークポイントのIDです。
一時的にブレークポイントを「無効化/有効化」したい場合は、disable breakpoints 1
やenable breakpoints 1
のように、disable/enable breakpoints <ID>
の形式でコマンドを入力します。
(byebug) disable breakpoints 1
(byebug) info breakpoints
Num Enb What
1 n at /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb:6
breakpoint already hit 1 time
2 y at /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb:2
breakpoint already hit 1 time
(byebug) enable breakpoints 1
(byebug) info breakpoints
Num Enb What
1 y at /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb:6
breakpoint already hit 1 time
2 y at /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb:2
breakpoint already hit 1 time
(byebug)
上の表示を見てもらえばわかるとおり、無効になっているブレークポイントはinfo breakpoints
を実行したときにEnb
列の値がn
になります。
ブレークポイントを削除する場合はdelete 1
のようにdelete <ID>
の形式でコマンドを入力します。
(byebug) delete 1
(byebug) info breakpoints
Num Enb What
2 y at /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb:2
breakpoint already hit 1 time
ちなみに、disable breakpoints
やdelete
のように、IDを指定しなかった場合はすべてのブレークポイントの設定を変更したり、削除したりします。
また、info/enable/disable breakpoints
はi/en/dis b
と省略することもできます。
条件付きのブレークポイントを指定する
さて、もう一度restart
コマンドを入力してプログラムを最初に戻します。
最初に戻すとブレークポイントもリセットされます。
(byebug) restart
Re exec'ing:
/Users/jit/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/byebug-9.0.6/bin/byebug /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/test/fizz_buzz_test.rb
=> 1: require './lib/fizz_buzz'
2: require 'minitest/autorun'
3:
4: class FizzBuzzTest < Minitest::Test
5: def test_fizz_buzz
6: assert_equal '1', fizz_buzz(1)
7: assert_equal '2', fizz_buzz(2)
8: assert_equal 'Fizz', fizz_buzz(3)
9: assert_equal '4', fizz_buzz(4)
10: assert_equal 'Buzz', fizz_buzz(5)
(byebug) i b
No breakpoints.
今度は条件付きのブレークポイントを設定していましょう。
たとえば、fizz_buzz
メソッドの引数n
に"4"が渡された場合だけ停止したいときは、次のようにif
を付けて条件を指定します。
break ./lib/fizz_buzz.rb:2 if n == 4
実際にやってみましょう。
(byebug) break ./lib/fizz_buzz.rb:2 if n == 4
Successfully created breakpoint with id 1
(byebug) c
Run options: --seed 30283
# Running:
Stopped by breakpoint 1 at /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb:2
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb
1: def fizz_buzz(n)
=> 2: if n % 15 == 0
3: 'Fizz Buzz'
4: elsif n % 3 == 0
5: 'Fizz'
6: elsif n % 5 == 0
7: 'Buzz'
8: else
9: n.to_s
10: end
(byebug) eval n
4
(byebug)
一番最後でeval n
を実行して引数のn
の値を確認しています。
確かにn
の値は4
になっているようです。
変数やメソッドの結果を確認する方法(evalを使うとき、使わないとき)
ところで、先ほどはeval n
のようにしてn
の値を確認しました。
実はeval
を使わずに、変数の値やメソッドの結果を確認することも可能です。
たとえばn % 15
のようにすれば、n
を15で割った余りを確認できます。
(byebug) n % 15
4
ただし、Byebugは次のように入力されたコマンドを解釈します。
- まず、Byebugのコマンドとして解釈することを試みる
- もしByebugのコマンドでなければ、Rubyのコマンドとして実行する
先ほど出てきた引数のn
はByebugコマンドのnext
の省略形と同じです。
そのため、n
だけ入力するとnext
コマンドが入力されたと解釈されてしまいます。
このように変数名やメソッド名がByebugコマンドとかぶってしまう場合は、eval
を使って値を確認する必要があります。
参考までにn
だけ入力した場合と、eval n
と入力した場合の挙動の違いを以下に載せておきます。
(byebug) n
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb
1: def fizz_buzz(n)
2: if n % 15 == 0
3: 'Fizz Buzz'
=> 4: elsif n % 3 == 0
5: 'Fizz'
6: elsif n % 5 == 0
7: 'Buzz'
8: else
9: n.to_s
10: end
(byebug) eval n
4
listで停止中の周辺コードを表示する
list
コマンドを使うと停止中の周辺コードを表示できます。
デバッガ内であれこれ試しすぎて「あれ?今どこのコードにいるんだっけ?」というときに使うと良いでしょう。
(byebug) list
[2, 11] in /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb
2: if n % 15 == 0
3: 'Fizz Buzz'
=> 4: elsif n % 3 == 0
5: 'Fizz'
6: elsif n % 5 == 0
7: 'Buzz'
8: else
9: n.to_s
10: end
11: end
(byebug)
quit/q!でByebugを終了させる
quit
コマンドを使うと、Byebugの実行を終了させることができます。
ただし、本当に終了するかどうかプロンプトで確認されます。
(byebug) quit
Really quit? (y/n) y
$
確認無しで終了する場合はq!
と入力します。
(quit unconditionally
と入力しても同じですが、q!
の方が短くて便利でしょう)
(byebug) q!
$
byebugメソッドでコード内にブレークポイントを埋め込む
今回使用しているサンプルコードはターミナルからruby
コマンドで起動できるため、byebug
コマンドをruby
コマンドの代わりとして代用できました。
しかし、ruby
コマンドで起動できないプログラム(Ruby on Railsの開発サーバー等)だと、byebug
コマンドを使うことができません。
こういうケースではソースコード内にbyebug
メソッドを埋め込みます。
プログラムの実行中にbyebug
メソッドが呼び出されると、そこでプログラムの実行を停止してByebugを起動することができます。
たとえばこんな感じです。
まず、fizz_buzz.rb
の2行目に、
require 'byebug'; byebug
という行を追加します。
def fizz_buzz(n)
require 'byebug'; byebug
if n % 15 == 0
'Fizz Buzz'
elsif n % 3 == 0
'Fizz'
elsif n % 5 == 0
'Buzz'
else
n.to_s
end
end
この状態でbyebug
コマンドではなく、普通にruby
コマンドでプログラムを実行してください。
$ ruby ./test/fizz_buzz_test.rb
Run options: --seed 27960
# Running:
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb
1: def fizz_buzz(n)
2: require 'byebug'; byebug
=> 3: if n % 15 == 0
4: 'Fizz Buzz'
5: elsif n % 3 == 0
6: 'Fizz'
7: elsif n % 5 == 0
8: 'Buzz'
9: else
10: n.to_s
(byebug)
するとご覧のとおり、byebug
メソッドを呼びだした次の行でプログラムの実行が停止しました。
あとはこれまでと同じようにByebugを実行できます。
なお、デバッグが終わったらブレークポイント用に追加したbyebug
メソッドは忘れずに削除してください。
-rbyebugを指定してコード中のrequireを省略する
ちなみにruby
コマンドに-rbyebug
という引数を付けると、最初からbyebug
がrequire
されるので、先ほどのコードからrequire 'byebug';
を省略できます。
def fizz_buzz(n)
byebug
if n % 15 == 0
'Fizz Buzz'
elsif n % 3 == 0
'Fizz'
elsif n % 5 == 0
'Buzz'
else
n.to_s
end
end
以下は-rbyebug
という引数を付けて上のプログラムを実行した結果です。
これでも問題なくByebugが起動していますね。
$ ruby -rbyebug ./test/fizz_buzz_test.rb
Run options: --seed 34342
# Running:
[1, 10] in /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb
1: def fizz_buzz(n)
2: byebug
=> 3: if n % 15 == 0
4: 'Fizz Buzz'
5: elsif n % 3 == 0
6: 'Fizz'
7: elsif n % 5 == 0
8: 'Buzz'
9: else
10: n.to_s
(byebug)
参考:pry-byebugを使う
ところで、ネット上でByebugの情報を検索すると、pry-byebugの情報もたくさん見つかります。
pry-byebugは文字通り、Pry(プライ)とByebugを組み合わせて使うgemです。
では、Pryは何なのかというと、irbよりもリッチでカラフルなREPL(対話型実行環境)です。
pry-byebugを使うと、リッチでカラフルなPryの環境を活かしながら、Byebugでデバッグすることができます。
とはいえ、Pryを使うかどうかは各自の好みなので、pry-byebugの使用が必須になるわけではありません。
pry-byebugを使いたい場合は、まずpry-byebugをインストールします。
$ gem install pry-byebug
Successfully installed pry-byebug-3.4.2
1 gem installed
次に、コードを停止したい行で次のコードを埋め込みます。
require 'pry'; binding.pry
試しに上のコードをこのチュートリアルで使ったfizz_buzz.rb
に埋め込んでみましょう。
def fizz_buzz(n)
require 'pry'; binding.pry
if n % 15 == 0
'Fizz Buzz'
elsif n % 3 == 0
'Fizz'
elsif n % 5 == 0
'Buzz'
else
n.to_s
end
end
この状態でプログラムを実行します。
$ ruby ./test/fizz_buzz_test.rb
Run options: --seed 61999
# Running:
From: /Users/jit/dev/sandbox/byebug-sandbox/lib/fizz_buzz.rb @ line 3 Object#fizz_buzz:
1: def fizz_buzz(n)
2: require 'pry'; binding.pry
=> 3: if n % 15 == 0
4: 'Fizz Buzz'
5: elsif n % 3 == 0
6: 'Fizz'
7: elsif n % 5 == 0
8: 'Buzz'
9: else
10: n.to_s
11: end
12: end
[1] pry(#<FizzBuzzTest>)>
ご覧のとおり、プログラムが途中で停止してPryが起動しました。
Byebugの画面に似ていますが、よく見ると微妙に出力内容が違います。
また、実際の画面を見るとシンタックスハイライトが効いてきれいに色分けされています。
pry-byebugの注意点
あとはByebugと同じように操作すればOK・・・と言いたいところですが、pry-byebugはByebugと微妙に使えるコマンドが違ったりします。
たとえばpry-byebugではrestart
コマンドが使えません。
[7] pry(#<FizzBuzzTest>)> restart
NameError: undefined local variable or method `restart' for #<FizzBuzzTest:0x007fadf892f5d8>
from (pry):5:in `fizz_buzz'
pry-byebugの実行中にhelp Byebug
コマンドを入力すると、pry-byebugで使用可能なByebugのコマンドを確認できます。
(ただし、これは素のByebugで使用可能なコマンドよりも少ないです)
[8] pry(#<FizzBuzzTest>)> help Byebug
Byebug
backtrace Display the current stack.
break Set or edit a breakpoint.
continue Continue program execution and end the pry session.
down Move current frame down.
finish Execute until current stack frame returns.
frame Move to specified frame #.
next Execute the next line within the current stack frame.
step Step execution into the next line or method.
up Move current frame up.
また、デフォルトではn
やc
のようなコマンドの短縮形が使えません。
コマンドの短縮形を使いたい場合は設定ファイルとして~/.pryrc
を作成し、その中に自分でエイリアスを定義する必要があります。
# 自分でByebug用のエイリアスを定義する
if defined?(PryByebug)
Pry.commands.alias_command 'c', 'continue'
Pry.commands.alias_command 's', 'step'
Pry.commands.alias_command 'n', 'next'
Pry.commands.alias_command 'f', 'finish'
end
pry-byebugは「Byebugが使えるPry」なので、pry-byebugを使いこなすためにはPryの使い方も理解しておく必要があります。(たとえば、!!!
で強制終了できる、等)
ただ、個人的には無理にpry-byebugを使わなくても、素のByebugで十分かなと思うので、pry-byebugに関する説明はこの程度にしておきます。
(普段からPryをよく使う人は、pry-byebugについてもっと研究する価値があるかもしれません)
参考:Ruby on RailsでByebugを使う
ここまではシンプルなRubyプログラムを対象にしてByebugの使い方を説明してきましたが、Ruby on RailsでもByebugを使うことは可能です。
そもそもRailsでは最初から開発環境とテスト環境でByebugが使えるようになっています。
チュートリアル用のサンプルコードはありませんが、RailsでByebugを使う手順を簡単に説明しておきます。
まず、Gemfileを開いて、byebug gemが追加されていることを念のため確認してください。
もしなければ追加して、bundle install
を実行します。
group :development, :test do
gem 'byebug', platform: :mri
end
なお、Windowsユーザーの方(RubyInstallerでインストールした方)はplatform
オプションを外してからbundle install
してください。
group :development, :test do
gem 'byebug'
end
次に、Byebugを起動したいところでbyebug
メソッドを埋め込みます。
class User < ApplicationRecord
def full_name
byebug
"#{first_name} #{last_name}"
end
end
それから普通にrails server
を起動します。
$ rails server
=> Booting Puma
=> Rails 5.0.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.6.2 (ruby 2.4.0-p0), codename: Sleepy Sunday Serenity
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
あとは、byebug
メソッドを埋め込んだコードが実行されるように画面を操作します。
byebug
メソッドが実行されるとRailsが停止し、サーバーを起動したターミナル上でByebugが操作できるようになります。
(ちなみにブラウザはプログラムが再開するまでレスポンスを待機し続けます)
[1, 10] in /Users/jit/dev/private/awesome-app/app/models/user.rb
1: class User < ApplicationRecord
2: def full_name
3: byebug
=> 4: "#{first_name} #{last_name}"
5: end
6: end
(byebug)
MinitestやRSpecのようなテストコードから実行する場合も同じです。
テストコードの実行中にbyebug
メソッドが呼び出されると、ターミナル上でByebugを操作できます。
$ bundle exec rspec
[1, 10] in /Users/jit/dev/private/awesome-app/app/models/user.rb
1: class User < ApplicationRecord
2: def full_name
3: byebug
=> 4: "#{first_name} #{last_name}"
5: end
6: end
(byebug)
今回説明したByebugコマンドのまとめ
このチュートリアルで出てきたByebugのコマンドを表にまとめます。
Byebugのコマンドは他にもまだありますが、とりあえずこれぐらい知っておけば、printデバッグよりも柔軟で効率の良いデバッグができるはずです。
コマンド | 短縮形 | 説明 |
---|---|---|
continue | c または cont | プログラムの実行を再開する |
help | h | コマンドのヘルプを表示する |
step | s | ステップ実行する(ステップイン) |
finish | fin | ステップアウトする |
restart | プログラムを最初からやり直す | |
next | n | ステップ実行する(ステップオーバー) |
(リターン) | 直前のコマンドを繰り返す | |
break | b | ブレークポイントの追加 |
info breakpoints | i b | ブレークポイントの一覧表示 |
enable breakpoints | en b | ブレークポイントの有効化 |
disable breakpoints | dis b | ブレークポイントの無効化 |
delete | del | ブレークポイントの削除 |
eval | 変数やメソッドの値を確認する | |
list | l | 停止中の周辺コードを表示する |
quit | q | Byebugの終了(確認あり) |
quit unconditionally | q! | Byebugの終了(確認なし) |
Byebugについてもっと学ぶ
Byebugには様々なコマンドや使い方があります。
Byebugについてもっと学ぶ場合は、公式リポジトリのREADME.mdやGUIDE.mdを読んでください。
- https://github.com/deivid-rodriguez/byebug/blob/master/README.md
- https://github.com/deivid-rodriguez/byebug/blob/master/GUIDE.md
pry-byebugの公式リポジトリはこちらにあります。
また、この記事でも説明したとおり、Byebugの実行中にhelp
コマンドを入力することでも、Byebugの各コマンドの使い方を学ぶことができます。
(byebug) help
break -- Sets breakpoints in the source code
catch -- Handles exception catchpoints
condition -- Sets conditions on breakpoints
continue -- Runs until program ends, hits a breakpoint or reaches a line
debug -- Spawns a subdebugger
delete -- Deletes breakpoints
disable -- Disables breakpoints or displays
display -- Evaluates expressions every time the debugger stops
down -- Moves to a lower frame in the stack trace
edit -- Edits source files
enable -- Enables breakpoints or displays
finish -- Runs the program until frame returns
frame -- Moves to a frame in the call stack
help -- Helps you using byebug
history -- Shows byebug's history of commands
info -- Shows several informations about the program being debugged
interrupt -- Interrupts the program
irb -- Starts an IRB session
kill -- Sends a signal to the current process
list -- Lists lines of source code
method -- Shows methods of an object, class or module
next -- Runs one or more lines of code
pry -- Starts a Pry session
quit -- Exits byebug
restart -- Restarts the debugged program
save -- Saves current byebug session to a file
set -- Modifies byebug settings
show -- Shows byebug settings
source -- Restores a previously saved byebug session
step -- Steps into blocks or methods one or more times
thread -- Commands to manipulate threads
tracevar -- Enables tracing of a global variable
undisplay -- Stops displaying all or some expressions when program stops
untracevar -- Stops tracing a global variable
up -- Moves to a higher frame in the stack trace
var -- Shows variables and its values
where -- Displays the backtrace
(byebug) help break
b[reak] [file:]line [if expr]
b[reak] [module::...]class(.|#)method [if expr]
They can be specified by line or method and an expression can be added
for conditionally enabled breakpoints.
Sets breakpoints in the source code
(byebug)
まとめ
というわけでこの記事ではByebugを使ったRubyプログラムのデバッグ手法について説明しました。
IDEが無いと、なんとなく「Rubyでデバッガを使うのは難しそう」と思ってしまうかもしれませんが、一度手順を覚えればそれほど大変ではありません。
これまでデバッガを使ったことがなかったという人は、ぜひこのチュートリアルを使って「デバッガに対する心理的なハードル」を飛び越えてください!
あわせて読みたい
開発中に例外や不具合に遭遇したときの対処方法を説明した記事です。
この記事とあわせて読むとデバッグの効率がさらによくなると思います。
プログラミング初心者歓迎!「エラーが出ました。どうすればいいですか?」から卒業するための基本と極意(解説動画付き) - Qiita