見逃しているかもしれないIntelliJの高度なデバッガー機能
2023年1月30日
0 分で読めます最近、デバッグの書籍とデバッグのコースを書き終えました。その結果、デバッグでお気に入りの機能についてよく質問されるようになりました。デバッグはIDEのデバッガーだけではありません。実際、書籍のうちこの側面を扱っているのは最初の章だけです。それでも、デバッグと聞くと、頭に浮かぶのはIDEです。しかし、こうした素晴らしいツールには、まだまだ発見すべき隠れた機能がたくさんあります。
その根本的な理由は単純です。私たちはデバッグの方法を学んでこなかったのです。デバッグの知識はテストできないため、大学でも教えられていません。
デバッグは仕事をしながら身につけていきますが、これは望ましいことではありません。デバッグをゴミ出しのように扱う開発者がいるのも無理はありません。鼻をつまみ、早く片付けようとドアまで駆けていくようなものです。デバッグは単なる作業の寄せ集めではありません。デバッガーは、仕事への深い洞察を得るのに役立つ素晴らしいツールです。アプリケーションの内部動作をこれまでとはまったく違う視点で明らかにし、ほかのどんな技術でも得られないレベルの理解をもたらしてくれます。
この記事では、このテーマについて講演する際に特に好評だったデバッガー機能を3つご紹介します。これらの機能はほとんどのJetBrains IDEで使えるはずですが、残念ながらVS Codeでは3つとも利用できません。VS Codeチームが環境をシンプルにするため、意識的にユーザー体験を選択した結果だと思いますが、この判断が見直されることを願っています。
IntelliJのデバッグ機能3選:
マーカーオブジェクトを使ったデバッグ設定
エントリーのレンダラー
メモリの追跡
マーカーオブジェクトを使ったIntelliJのデバッグ設定
デバッグ中の大きな課題のひとつは、作業内容を把握し続けることです。ブレークポイントで停止すると、オブジェクトが表示されます。これは前に見たオブジェクトでしょうか?参照、値、ID、ポインターアドレスは同じでしょうか?
以前は、確認した内容を覚えておき、新しいアドレスではないことを確かめるために、ポインターアドレスを紙に書き留めていました。これは面倒で混乱します。自分の字すら読めないこともあります……もっといい方法はないのでしょうか?
あります。デバッガーでオブジェクトを調べるとき、値を書き留める代わりに、コンテキストメニューからマークできます。

このオプションを選択すると、オブジェクトの名前を入力するよう求められます。このダイアログは少し分かりにくいものです。ウォッチエントリーに別名を付けるだけだと思うかもしれませんが、そうではありません。

ここで行っているのは、新しいグローバル変数の定義です。スコープに関係なくウォッチで値を確認できるだけでなく(これだけでも便利です)、条件文でも利用できます。そのため、値が変わったときだけブレークポイントで停止させることができます。ここでその動作を確認できます。

これは非常に強力な機能です。現在のスレッドにマークを付けることで、スレッド関連の問題を検出できます。また、IDは同じでもポインターが異なるオブジェクトの追跡にも使えます。たとえばオブジェクトリレーショナルマッピングでは、同じエンティティのインスタンスが2つ同時に存在することがあります。こうしたバグの追跡は非常に困難です。
エントリーのレンダラー
ウォッチ領域は、最新のIDEが備える機能の中でも特に優れたものです。コードをステップ実行すると、必要な情報をひと目で確認できます。しかし、その「ひと目」が、変数を展開して複雑な階層を深く掘り下げ、必要な値を探し出す作業になってしまうこともあります。
Javaでよく使われる回避策は、より詳しい値を返すようにtoString()をオーバーライドすることです。しかし、そうしたくない理由はいくつかあります。
対象が自分のオブジェクトではなく、フレームワークで定義されたオブジェクトかもしれません。
自分だけに必要な変更かもしれません。ほかの人に影響する形で
toString()を変更したくありません。toString()は重要で、アプリケーションのパフォーマンスに大きな影響を与える可能性があります(ログ出力のコストなど)。toString()では機能が足りないこともあります。要素のリストを含む変数を調べたい場合はどうすればよいでしょうか?
レンダラーを使えば、デメリットを避けながらウォッチ領域の機能を活用できます。少し改変したSpring Pet Clinicのデモで説明します。このデモでは、データの保存にJava Persistence API(JPA)を使用しています。Spring Dataにはリポジトリという概念があり、リポジトリはデータベースのテーブルのようなものと考えられます。リポジトリはより広い抽象概念なので厳密には同じではありませんが、一般的には近いものとして捉えられます。
ブレークポイントで停止すると、次の画像のような役に立たない情報が表示されることがよくあります。visitRepositoryはリポジトリですが、デバッグに役立つ情報が何もありません。テーブルにはいくつの要素があり、それぞれどんな値でしょうか?
ウォッチエントリーをさらに展開することもできますが、手がかりは得られません。このプロキシオブジェクトからは、ウォッチに役立つ情報が得られないのです。

レンダラーを使えば、もっと便利になります。まず、ウォッチ領域を右クリックしてデータビューのカスタマイズを選択します。
![[Customize Data Views]をクリックして、レンダラーの設定を開始します。](https://res.cloudinary.com/snyk/image/upload/f_auto,w_2560,q_auto/v1675114215/blog-intellij-debugger-customize-data-views.jpg)
次のダイアログでレンダラーの要素を定義すると、はるかに役立つ情報を表示できます。ウォッチ領域では、要素数をひと目で確認でき、必要に応じて各要素を個別に調べられるようになります。

左側に設定例を示します。JPARepositoryをカスタマイズするよう選択しています。ウォッチ領域のperRepositoryは別のリポジトリ型なので、影響を受けていないことに注目してください。次に、レンダリングを実行するコードを定義します。オブジェクトのcount()メソッドを呼び出してオブジェクト数を取得し、ウォッチ領域に表示する文字列を作成します。
さらに便利なのが2つ目の部分です。findAll()を使ってリポジトリから要素を取得し、オブジェクトを展開したときに表示できます。最後の部分では、リポジトリの横にプラス記号を表示して、展開できるようにするかどうかを指定します。
すばらしい機能です。ただし、大きな欠点がひとつあります。定義したオブジェクト型ごとに設定が必要なのです。面倒ですね……でも、本当にそうでしょうか?
こうした設定を表すアノテーションを定義すれば、このような解決策をライブラリの配布物に直接組み込めます。これにより、サードパーティライブラリのデバッグがずっと簡単になります。
メモリの追跡
メモリの話をすると、プロファイリングツールが思い浮かびます。メモリを追跡するのに、これ以上の方法があるでしょうか?

プロファイラーは間違いなく優れたツールです。しかし、幅広い用途を想定したものです。バグを絞り込む段階で必要となる、細かな仕上げのための解決策がありません。また、特定のオブジェクトを追跡したり、システムを詳細に確認したりする用途にも向いていません。たとえば、ウォッチ領域の右上をクリックして「メモリ」を有効にすると、メモリビューを表示できます。

有効にすると、メモリ上のオブジェクト一覧が表示されます。各オブジェクトをダブルクリックすれば、その中に含まれるオブジェクトの一覧を確認できます。また、「差分」列では、直前のブレークポイントから現在のブレークポイントまでの間に割り当てられた、型ごとのオブジェクト数も確認できます。

これだけでもすばらしい機能ですが、限界もあります。特定の時点でどのオブジェクトが割り当てられたのかは分かりません。それを把握するには、IDEがあらゆる型のオブジェクト割り当てをすべて追跡し、これまでに作られたオブジェクトをすべて参照し続ける必要があります。それは現実的ではありません。しかし、特定のオブジェクト型を選んで右クリックし、新しいインスタンスを追跡を選択できます。
上のスクリーンショットではjava.lang.Stringに対して設定しました。横にあるウォッチアイコンはその印です。これにより、あるブレークポイントから次のブレークポイントまでの間に割り当てられた、個々のオブジェクトを確認できます。システムコードが内部で何をしているのかが分かるため、非常に役立ちます。さらに、もうひとつ機能があります……
この設定を行うと、オブジェクトの割り当てごとに完全なスタックトレースを取得し、割り当てを引き起こした行を確認できます。各割り当ての内容と、その理由を理解するのに役立ちます。規模が大きく、なじみのないシステムの内部で何が起きているのかを把握できます。
プロファイラーを補完するのに最適な機能です。
最後に
この記事で、目指していた「すごい!」という反応を引き出せていたらうれしいです。開発者は、デバッグに必要以上に不安を感じているように思います。その多くは、ツールやプロセス、適切なメンタリングが不足していることに起因します。
デバッグは、心をすっきりさせてくれる体験であるべきです。謙虚な気持ちになり、デバッグ中は誰もが初心者のように感じますが、それは悪いことではありません。難しさはあっても、楽しくわくわくするプロセスであるべきです。手元にあるツールを大切にし、その力を最大限に活用しましょう。
次に見つけたバグを、ひき逃げのように扱わないでください。冒険として向き合いましょう。ガレージから新しい道具を取り出し、新たなスキルを使ってバグに挑む冒険です。
