Help us understand the problem. What is going on with this article?

printデバッグにさようなら!Ruby初心者のためのByebugチュートリアル

More than 3 years have passed since last update.

はじめに

この記事は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を投稿してください。

printデバッグとデバッガは何が違うの?

チュートリアルを開始する前にprintデバッグとデバッガを使ったデバッグの違いを簡単に説明しておきます。

まず、printデバッグはprint文(ただしRubyの場合はputspが主流なはず)をコード内に埋め込んで、変数の値や実行された条件分岐を確認するデバッグ手法のことです。

例を挙げると以下のようなコードになります。
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を使うとこんな感じでデバッグできます。

GkUnVRX9gK.gif

もちろん、Byebugの使い方はこのあとで詳しく説明するので、今はよくわからなくても問題ありません。

なぜByebugなの?

Rubyには標準のdebugライブラリも存在しています。

https://docs.ruby-lang.org/ja/latest/library/debug.html

しかし、Twitter上でアンケートを採ったところ、標準のdebugライブラリよりもByebugを使っている人の方が多数派でした。

なので、この記事ではRubyプログラマの中で最もポピュラーなByebugを説明の対象としています。

おことわり

実は僕は(こんな記事を書いておきながら)普段ほとんどByebugを使っていません。
僕がデバッグでよく使っているのはRubyMineです。

Byebugをよく使っている人がこの記事を読んで、明らかに間違っている点を見つけたり、「これは絶対説明しておくべき!」という便利機能を知っていたりしたら、編集リクエストやコメント欄等で優しく指摘してやってください。

それではここからチュートリアルのスタートです!

このチュートリアルで使用するサンプルコードを準備する

このチュートリアルでは以下の2つのファイルを使います。
1つ目はFizz Buzz問題を解く、プログラム本体です。

lib/fizz_buzz.rb
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のコードです。

test/fizz_buzz_test.rb
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コマンドはcontcと短縮できるようです。
また引数に数字を渡すと、その行で処理が止まるようですね。

試しに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の短縮形はsfinishの短縮形は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 2finish 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 1enable 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 breakpointsdeleteのように、IDを指定しなかった場合はすべてのブレークポイントの設定を変更したり、削除したりします。

また、info/enable/disable breakpointsi/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

という行を追加します。

lib/fizz_buzz.rb
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という引数を付けると、最初からbyebugrequireされるので、先ほどのコードからrequire 'byebug';を省略できます。

lib/fizz_buzz.rb
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に埋め込んでみましょう。

lib/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の画面に似ていますが、よく見ると微妙に出力内容が違います。

また、実際の画面を見るとシンタックスハイライトが効いてきれいに色分けされています。

Screen Shot 2017-01-16 at 6.43.45.png

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.

また、デフォルトではncのようなコマンドの短縮形が使えません。
コマンドの短縮形を使いたい場合は設定ファイルとして~/.pryrcを作成し、その中に自分でエイリアスを定義する必要があります。

~/.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を実行します。

Gemfile
group :development, :test do
  gem 'byebug', platform: :mri
end

なお、Windowsユーザーの方(RubyInstallerでインストールした方)はplatformオプションを外してからbundle installしてください。

Gemfile
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を読んでください。

pry-byebugの公式リポジトリはこちらにあります。

https://github.com/deivid-rodriguez/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

jnchito
SIer、社内SEを経て、ソニックガーデンに合流したプログラマ。 「プロを目指す人のためのRuby入門」の著者。 http://gihyo.jp/book/2017/978-4-7741-9397-7 および「Everyday Rails - RSpecによるRailsテスト入門」の翻訳者。 https://leanpub.com/everydayrailsrspec-jp
https://blog.jnito.com/
sonicgarden
「お客様に無駄遣いをさせない受託開発」と「習慣を変えるソフトウェアのサービス」に取り組んでいるソフトウェア企業
http://www.sonicgarden.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away