55
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ビビッドガーデンAdvent Calendar 2021

Day 13

debug.gemを使ったことがない方に向けて、いくつかの機能を紹介する

Last updated at Posted at 2021-12-12

こんにちは、@yuskuboです。
本記事は ビビッドガーデン Advent Calendar 2021 の13日目の記事です。

概要

RubyKaigi 2021でRubyの新しいdebuggerであるdebug.gemが紹介されましたね!Ruby 3.1からは標準ライブラリになる予定とのことです。
ビビッドガーデンでは、これまでbyebug gemとpry gemを利用していましたが、今後のバージョンアップ対応のことも考慮してdebug.gemへ移行し、日々の業務で使っています。
本記事では、私がよく使っているdebug.gemの機能をいくつかピックアップして紹介していきます。「それ知らなかった!」や「そんなこともできたんだ!」ということを1つでもお届けできれば嬉しいです。

対象読者

  • debug.gemをまだ使ったことないから基本的な使用方法を知りたいという方
  • debug.gemは既に使っているけどbinding.break以外は知らないから、他のコマンドも知りたいという方

前提事項

  • 本記事では、Ruby on Railsのサンプルアプリケーションでdebug.gemを使いながらご紹介します。
  • サンプルアプリケーションを用意する手順も書いていますが、ご自身のPCでRuby on Railsを利用できる状態であることを前提としています。Ruby on Railsの環境構築から行いたい場合は、以下のRailsガイドなどを参照して行ってみてください。

  • debug.gemを使うには、Ruby 2.6以降のバージョンである必要があります。
  • RubyやRuby on Rails自体の解説は行っていません。

事前準備

Ruby on Railsのサンプルアプリケーションを用意していきます。既に手元にRuby on Railsを動かせる環境がある方は、スキップして問題ありません。

Railsプロジェクトを新規作成

bash
$ rails new debug_sample_app

Gemfileにdebug.gemを追加

Gemfile
group :development, :test do
  gem 'debug'
end

各コマンドの実行

Gemのインストール、マイグレーションの実行、コンパイルを行い、起動します。
debug.gemを使うことが目的なので、scaffoldを使ってUserというリソース一式を作成しています。

bash
$ bundle install

$ bin/rails generate scaffold User name:string email:string

$ bin/rails db:migrate

$ bin/rails webpacker:compile

$ bin/rails s

画面へのアクセス

ユーザー一覧画面のhttp://127.0.0.1:3000/users にアクセスし、以下の画面が表示されればサンプルアプリケーションの準備は完了です。問題なく起動できたら、「New User」から1名ユーザーを作成しておきましょう。
new_user.png

デバッグ用のメソッド追加

メソッド呼び出しがあった方がデバッグを試しやすいので、userモデルにhas_email?メソッドを追加し、usersコントローラーから呼び出しておきます。あくまでデバッグを試すためのものなので、処理自体は何でも大丈夫です。ここではuserのメールアドレスが存在するかどうかをチェックしています。
また、showアクションの中で処理を止めて色々試したいので、showアクションをbefore_actionの対象から外しておきます。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  # onlyに指定した配列からshowを削除
  before_action :set_user, only: %i[ edit update destroy ]

  # GET /users or /users.json
  def index
    @users = User.all
  end

  # GET /users/1 or /users/1.json
  def show
    # 以下の2行を追加
    @user = User.find(params[:id])
    @has_email = @user.has_email?
  end
# ~~ 以下省略 ~~
app/models/user.rb
class User < ApplicationRecord
  # 以下のメソッドを追加
  def has_email?
    self.email.present?
  end
end

デバッグコマンドを使ってみる

それでは早速デバッグコマンドを使ってみましょう!

binding.break / binding.b / debugger

プログラムを止めたい箇所にbinding.breakを記述すると、ブレークポイントとして処理を止めることができます。binding.breakのエイリアスとして、binding.bdebuggerも用意されているので、好きなものを使いましょう。
試しに、users_controllerのshowアクションにbinding.breakを追記して処理を止めてみます。

app/models/user.rb
def show
  @user = User.find(params[:id])
  # ブレークポイントを追加
  binding.break
  @has_email = @user.has_email?
end

http://127.0.0.1:3000/users/1 へアクセスすると、以下のようにデバッグコンソールが表示されます。12行目で処理が止まっていることがわかります。
binding_break.png

例えば、ここで@userと入力すると、@user変数の中身を見ることができます。
binding_break_user.png

また、ここで1 + 1と入力すると、rubyの式として評価され2という結果を得ることができます。
デバッグコンソールで1+1を実行
何も入力せずにEnterを押した場合は、最後に実行したコマンドや式(ここでは1 + 1)が繰り返し実行されます。何度もステップオーバーやステップインする時に使うと同じコマンドを繰り返し入力する必要がないので楽ですね!
デバッグコンソールでEnterを実行
ブレークポイントでの確認が終わったらcontinue(この後ご紹介するコマンド)を実行してください。continueを実行すると、最後まで処理が実行されて、ブラウザにユーザー詳細画面が表示されます。

binding.break do: 'debug_command'

binding.breakは、doキーワードにデバッグコマンドを指定して使うこともできます。例えば、以下のように指定すれば、ActiveRecord::RecordNotFoundが発生した時のみブレークポイントで止めることができます。

app/controllers/users_controller.rb
def show
  @user = User.find(params[:id])
  binding.break do: 'catch ActiveRecord::RecordNotFound'
  @has_email = @user.has_email?
end

上記のように指定するとユーザーが存在するhttp://127.0.0.1:3000/users/1 へアクセスした時は処理は止まりません。
しかし、ユーザーが存在しないhttp://127.0.0.1:3000/users/2 へアクセスすると、ActiveRecord::RecordNotFoundが発生して処理が止まります。
binding_break_do_option.png
このように特定の条件で処理を止めてデバッグしたい時に使うと便利です!

next (n) / step (s) / continue (c)

以下の3つのコマンドはデバッグコンソール上での基本操作となるコマンドです。それぞれ意味は以下の通りです。

  • next:ステップオーバー(同じメソッド内で次の行へ進む)
  • step:ステップイン(別のメソッド呼び出しである場合、呼び出したメソッド内へ進む。メソッド呼び出しでない場合は、同じメソッド内で次の行へ進む。)
  • continue:後続の処理を実行

なお、それぞれ省略形も用意されていて、頭文字だけでコマンドを実行できます。例えば、nだけ入力すると右側に「# next command」と表示され、nextであることがわかります。細かい部分がとても親切ですね!
デバッグコンソールで頭文字を入力

info (i)

現在実行中のフレームのローカル変数、インスタンス変数、定数を表示することができます。ざっと変数や定数を確認したい時に便利です。省略形のiでも実行できます。
binding_break_info.png

outline (o) / ls

現在のスコープで利用可能なメソッド、定数、ローカル変数、インスタンス変数を表示できます。「ここであのメソッド使えるっけ?」となった時に役立ちます。省略形のoでも実行できますし、エイリアスとしてlsも用意されています。
binding_break_outline.png

frame (f) / list (l)

デバッグ中に変数の中身を見たり、式を実行したりしているうちに、「あれ?今どこで何してたんだっけ?」となることがあります。frameを実行すると、現在実行中のフレーム情報を表示することができます。これでusers_controllerの12行目でデバッグしていたことを確認できます。省略形のfでも実行できます。
binding_break_frame.png
さらに、「ブレークポイントを設定した周辺のソースコードはどんなものだったっけ?」を確認したい時は、listを実行します。省略形のlでも実行できます。
listを実行すると、現在実行中のフレーム情報周辺のソースコードを表示することができます。複数回実行すると、実行する度に後続の行を表示します。list -のように-オプションを付けると1つ前のフレーム情報を表示することができます。また、list 10-15のように表示する行数を指定することもできます。
binding_break_list.png

record

これはすごく便利だなと思ったのが、recordです。recordを有効にすると実行した処理を記録して、後から処理を遡って確認することができます。例えば、「5行前ではこの変数の値はどうなってるんだっけ?」のように前の状態を確認したいときに重宝します。
処理の記録を開始する時は、以下のようにブレークポイントで止めた後、record onで記録を開始します。その後、任意の場所までnextで処理を進めます。
binding_break_record_on.png
処理を進めた後にstep backすると、実行した処理を遡ることができます。step backしている間は、「replay」と表示されます。複数回step backしていると、userモデルやusersコントローラー内の見覚えのある処理まで遡ってきます。なお、戻り過ぎた時は、stepで処理を進めることもできます。
binding_break_step_back.png
確認が終わったらstep resetで、replayを終了し、ブレークポイントまで戻れます。
また、処理の記録を終了する時は、record offで記録が終了となります。
binding_break_record_off.png

trace

traceコマンドを使うと、トレース情報を表示することができます。trace lineを実行するとLineTracerが有効になり、トレース情報が表示されるようになります。
binding_break_trace_line.png
この状態で処理を進めてみると、以下のようにトレース情報が表示されます。赤枠の部分を見ると、usersコントローラーの13行目の次にuserモデルの3行目(has_email?メソッドの処理)が実行されていることがわかります。
binding_break_trace.png
トレース情報の表示が不要になったら、trace off lineで表示を終了することができます。
デバッグコンソールでtraceを終了

backtrace (bt)

backtraceコマンドを使うと、バックトレース情報を表示することができます。省略形のbtでも実行できます。ここではモデル側に用意したメソッド内にブレークポイントを設定して、バックトレース情報を確認してみます。

app/models/user.rb
class User < ApplicationRecord
  def has_email?
    # ブレークポイントを設定する
    binding.break
    self.email.present?
  end
end

backtraceを実行すると多くの情報が出てくるので、今回はbacktrace 5として、5行だけ表示してみます。has_email?メソッドがusersコントローラーから呼び出されたことがわかります。
binding_break_record_backtrace.png

config

configを使うとdebug.gemの設定内容を確認することができます。例えば、binding.breakで処理を止めた時に表示されるソースの行数(show_src_lines)はデフォルトでは10行であることがわかります。
binding_break_config.png
さらにconfig setを使うと設定内容を変更することができます。試しに表示する行数を変更してみましょう。config set show_src_lines 20と実行すると、表示を20行に変更することができます。
binding_break_config_set.png
表示行数の設定変更後に処理を止めてみると、20行表示されるようになっています。
binding_break_config_set_lines.png

なお、デバッグコンソールで変更した設定はRailsアプリケーションを再起動するとデフォルト値へ戻ってしまいます。常に反映させたい場合は、~/.rdbgrcという初期化スクリプトに記述する必要があります。初期化スクリプトはデバッグセッション開始時に読み込まれて記述した設定が反映されます。初期化スクリプトに以下のように記述しておくと、binding.breakで処理を止めた時に表示されるソースの行数は常に20行となります。

~/.rdbgrc
config set show_src_lines 20

help (h)

「あのコマンドどうやって使うんだっけ?」や「他にどんなコマンドがあるだろう?」というのが知りたくなったら、helpコマンドを使いましょう。全てのコマンドの使い方を表示することができます。省略形のhでも実行できます。また、help recordのようにコマンドを指定すれば、指定したコマンドの使い方だけ表示することもできます。
binding_break_help.png

その他補足事項

各コマンドの紹介時にinsのような省略形もご紹介しました。これらは1文字のデバッグコマンドであるため、ローカル変数としてinsが使われていたとしてもデバッグコマンドとして実行されます。ローカル変数の中身を確認したい場合は、p iとすることで表示することができます。
以下のようにローカル変数iを用意して、デバッグコンソールから確認してみます。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    # ローカル変数iを用意
    i = 'test'
    binding.break
    @has_email = @user.has_email?
  end

iだとinfoコマンドが実行されていますが、p iとするとローカル変数iの中身が表示されていることがわかります。
binding_break_p.png

まとめ

いかがだったでしょうか?「それ知らなかった」、「明日からそのコマンド使ってみよう」といったものが1つでもあったなら幸いです。debug.gemには便利で魅力的なコマンドがたくさん用意されているので、私もどんどん使い込んで行きたいと思います。

なお、本記事は、以下のdebug.gemのREADMEを元にいくつかの内容をピックアップしてご紹介したものです。最新の仕様や機能・コマンドを網羅的に知りたい場合は、debug.gemのREADMEをご確認ください。

また、RubyKaigi 2021のスライド資料や動画では、使用方法に加えて「開発された背景」や「どうやって動いているのか」なども解説されているので、ぜひこちらも合わせてご確認ください。

最後に

ビビッドガーデンが運営する食べチョクのバックエンドは、Ruby on Railsで開発されています。ビビッドガーデンではエンジニアを絶賛募集中ですので、気になった方はこちらもぜひ覗いてみてください!!

55
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?