Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

実証済みのデバッグ技術で Ruby と Rails のバグを迅速に解決する

ソフトウェアのバグは、開発現場に混乱をもたらし、姿を隠し、イライラさせ、あらゆる隙間に侵入してきます。問題を突き止めて修正するには、発明王エジソン並みの粘り強さが必要になることも少なくありません。

しかし、粘り強さだけでは十分ではありません。コードをデバッグするには「情報」も欠かせません。その不具合はどんな症状を引き起こすのか? 影響はどれほどか? 発生頻度は? 波及範囲は? 原因はどこにあるのか? コアダンプ、スタックトレース、ログ、テストケースといったバグの証拠や痕跡は、かけがえのない手がかりとなります。

本記事では、Ruby と Rails の開発者が今すぐ活用できる、問題の証拠を収集・調査するためのテクニックとツールを紹介します。データは粘り強さの代わりにはなりませんが、デバッグ作業を格段に楽にし(そして貴重な睡眠時間を守ってくれる)でしょう。

Rails のログ機能を最大限に活用する

Rails には、稼働中のアプリケーションの内部を覗き見るための優れたツールが揃っています。設定可能な Logger を使えば、Rails 自身が出力する診断情報に加えて、独自に追加したいログも記録できます。さらに、データベースクエリの発生源を特定する詳細なクエリログや、バックグラウンドジョブがどこでキューに入れられたかを明らかにする詳細なエンキューログも提供されています。後者の 2 つのログは development 環境ではデフォルトで有効になっており、わずか 2 行の設定で他の環境でも有効化できます。

あまり知られていませんが、Rails 7 以降では、各 SQL クエリにコメントを自動付与するオプションも利用できます。config/application.rb や任意の環境ファイルに設定を追加するだけで、クエリログにアプリケーション名、コントローラー名、アクション名、バックグラウンドジョブ名が付加されます。出力サンプルは Rails 公式のデバッグガイドでも確認できます。

とはいえ、本番環境ですべての機能を有効にするのは避けるべきです。ログ生成はメモリと時間という、負荷のかかるアプリケーションにとって有限のリソースを大量に消費するからです。

ただし例外もあります。特に開発モードやテストモードでのデバッグ時には、必要なときだけ一時的に診断機能を有効化したいはずです。その際は、コードを書き換えて再デプロイするのではなく、環境変数で状態を制御するのが賢いやり方です。Heroku のようなプラットフォームでは、環境変数の変更に再デプロイは不要です。

たとえば、ロギングと詳細度を制御する変数群を定義しておきます。利便性のため、設定値を照会する小さなクラスを作成しておくと、コードの見通しが良くなります。あとはこれらの変数を使って config/application.rb 内でロギング機能を設定すれば完成です。

各オプションの設定には、シェル、dotenv ファイル、CI、ホスティング・デプロイ基盤など、好きな手段を利用できます。必要になった瞬間だけ機能を有効化することも可能です。コマンドラインで環境変数を定義してアプリケーションを起動するだけです。

開発環境向けに Puma をチューニングする

前述の考え方を踏まえて、環境変数を使って開発環境のデバッグに合わせた Puma 設定を行う方法を見ていきましょう。

通常、Puma は本番環境でスループットを最大化するよう、複数のワーカーとワーカーごとの多数のスレッドで構成されます。開発環境ではその逆が理想です。つまり1 ワーカー、1 スレッド、そして対話型デバッグを許容する非常に長いタイムアウトです。これらはいずれも調整可能なパラメータなので、config/puma.rb を環境変数経由で切り替えられるように書き換えます。

こうすることで、3 つの環境変数だけで各環境の Puma を制御できます。開発環境では、対話型デバッグに最適化した値を設定しましょう。

Puma の設定を検証したい場合は、環境変数 PUMA_LOG_CONFIG=true を指定してアプリケーションを起動します。起動時にアクティブな設定が出力されます。

ちなみに、最近の Rails リリースにおける Puma のデフォルト設定は、ここで紹介した内容とほぼ同じになっています(Nate Matykiewicz 氏のヒントによるものです)。

バックグラウンドジョブをインラインで実行する

ある程度規模の大きな Rails アプリケーションは、計算量の多い処理や長時間かかるタスクをバックグラウンドジョブに任せるのが一般的です。ジョブは非同期で実行され、リクエスト/レスポンスサイクルから切り離されています。レポート生成、メール送信、サードパーティ API との連携などは「帯域外」処理の好例ですが、この非同期性こそがデバッグを難しくする原因にもなります。

トラブルシューティングを簡単にするには、ローカルの開発・テスト環境でジョブを即座に実行するのが効果的です。即時モードではジョブはキューに入れられず、その場で瞬時に実行されます。フォアグラウンドで動くため、ブレークポイントを仕掛けて状態を対話的に調査できます。

Active Job の定番キューバックエンドである Delayed Job を例に見てみましょう。Delayed Job にはキューイングの有効/無効を切り替える設定があり、デフォルトの true ではジョブは通常どおりキューに入りますが、false にすると即時実行されます。config/initializers/delayed_job.rb にその設定を追加し、環境変数 DELAYED_JOBS_DISABLE_JOB_QUEUES に何らかの値が入っていればキューイングを無効化、空または未定義なら有効化、という制御にします。シェルやドットファイルで値を設定・解除すれば、いつでも挙動を切り替えられます。

環境変数の命名に厳密なルールはありません。自分にとって意味のある名前を選びましょう。便利な慣習としては、パッケージ名をプレフィックスとして付ける方法があります。たとえば PUMA_ は Puma に影響する変数、DELAYED_JOB_ は Delayed Job が使う変数、という具合に分類でき、後から見ても分かりやすくなります。

ネットワーク通信の中身を覗き見る

バックグラウンドジョブと同様に、Rails アプリケーションは外部 API も利用します。GraphQL データベース、EC 取引、公開データソースなど、API 経由で外部サービスへアクセスするケースは多々あります。

もうひとつ、Net::HTTP ライブラリの HTTP リクエストとレスポンスを条件付きで出力する例を紹介します。環境変数 DEBUG_HTTP に空白以外の値が設定されていれば、送信リクエストと受信レスポンスが STDOUT に出力されます。確認するには、コマンドラインで環境変数を定義してアプリケーションを起動するだけです。

debug? メソッドは実際のフラグ実装を抽象化しており、これを含む一連のコードは、すべてのネットワークリクエストの基底クラスとして再利用できます。

今すぐ導入したいデバッグ用 Gem

RubyGems の世界は広大で、デバッグに役立つ優れた gem をすべて網羅することは不可能です。そこで、今日から追加できて即効性のあるツールを厳選して紹介します。おそらく、すべてのプロジェクトに入れたくなるはずです。

  • awesome_print / table_print: Active Record モデルを含む Ruby のデータ構造を、リッチで読みやすい形式に出力します。コード内でもコンソールでも使えます。標準の p の代わりに ap を呼ぶだけで、属性がアルファベット順・1 行 1 属性で整形表示されるなど、細部まで工夫された出力が得られます。

  • better_errors: 標準の Rails エラーページを、強化されたスタックバックトレース、パラメータ一覧、そして例外発生箇所のスタックフレームや変数を調査できるインタラクティブコンソールに置き換えます。ソースコードへのリンクをお気に入りエディタに関連付けることも可能です。Visual Studio Code と連携するには、設定後にシェルで export BETTER_ERRORS_EDITOR=vscode を実行して Rails サーバーを再起動します。以降、ファイルへのリンクが自動的に VS Code で開きます。

  • faker / factory_bot(およびその代替 gem): Gemfile の test グループと development グループの両方に入れておきましょう。コンソールで素早くテストデータを生成したい場面で、両者とも絶大な威力を発揮します。

もうひとつの提案です。アプリケーションモニタリングに AppSignal を利用しているなら、ログを解析してエラーの発生源を特定する機能が備わっています。詳細は「Debugging in Ruby with AppSignal」を参照してください。

デバッグはスキルである

コードのデバッグはある種の芸術です。あらゆる創造的な営みと同じように、練習を重ねるほど上達していきます。

ぜひ、別の開発者とペアを組んでデバッグしてみてください。特に経験豊富な開発者とのペアは効果的です。相手が同意してくれるなら、思考を声に出しながら、直感や洞察を交換し合うことで、一人では見つけられない解決策にたどり着けることがあります。

それでは、良いハッキングを!

P.S. Ruby Magic の記事を公開と同時にお読みになりたい方は、Ruby Magic ニュースレターをご購読ください。投稿を見逃すことはありません!


著者プロフィール:Martin Streicher

ゲスト執筆者の Martin Streicher はプロの Ruby 開発者です。Purdue 大学でコンピュータサイエンスの上位学位を取得し、『Linux Magazine』(米国版)編集長を 5 年間務めたほか、IBM の旧 developerWorks ポータルで連載された「Speaking in Unix」コラムの創刊筆者でもあります。コーディングや執筆の合間には、アートの収集と多くの小型犬たちの世話に勤しんでいます。

  1. Solid Queue 徹底解説:Ruby on Rails に最適な実績あるバックグラウンドジョブソリューション

    本シリーズの前回の記事では、バックグラウンドジョブを処理するシステムが必要な場合、Solid Queue が優れた選択肢であることを確認しました。すべてのジョブをデータベースに保存することで外部依存を最小限に抑えられる——つまり Redis が不要!——にもかかわらず、驚くほど高いパフォーマンスを発揮します。 しかし、本番環境で使えるバックグラウンドジョブシステムにとって、パフォーマンスが高いだけでは十分ではありません。Rails 開発者は長年の経験の中で、多くの機能を当たり前に期待するようになりました。単にジョブをキューに登録してバックグラウンドで実行したいだけでなく、将来の日時を指定したス

  2. Ruby on Rails × AppSignalで実現するトレーシング完全ガイド――パフォーマンスと信頼性を高める方法

    本ガイドでは、AppSignalを活用してRuby on Railsアプリケーションのパフォーマンスボトルネックを検出・診断・解消し、適切なトレーシングを実装する方法を解説します。トレーシングのセットアップから、エラーのキャプチャ、ロギングまで、必要な情報をすべて網羅しています。 この記事を読めば、どんなに高負荷な状況でもアプリケーションをスムーズに動作させられるようになります。 まずは、トレーシングとは何か、そしてそのメリットについて簡単に確認しましょう。 トレーシングとは? トレーシングとは、リクエストや操作がアプリケーション内をどのように流れていくかを追跡するプロセスです。Rubyアプリ