🤖

React開発者Dan Abramov氏の「あらゆるバグを修正するための汎用的な手法」

に公開

React開発者のDan Abramov氏の あらゆるバグを修正するための汎用的な手法 を読んで、基本的なことだけどやっぱり大事だよねと思ったので記事にします。

多くの方がご存知の通り開発現場では、AIに修正を依頼しても解決しないバグや、原因が全く見当がつかないバグに遭遇することが多々あります。そうした際に、闇雲に修正を試みるのではなく、「確実な再現手順の確立」と「体系的なコードの削減」 によって論理的にバグを追い詰める手法を解説します。

はじめに:なぜ「なんとなく修正」は失敗するのか

バグに直面した際、多くのエンジニア(そしてAI)は「ここが怪しい」という勘を頼りに修正を試みます。しかし、複雑なバグに対してこのアプローチをとると、修正したつもりでも直っていなかったり、時間を浪費したりすることがよくあります。

例えば、ある「スクロールがガタつく」というバグを例に、Dan氏が用いる確実なデバッグフローを見ていきましょう。

Step 0: 「とにかく直す(Just Fix It)」の罠

最初に陥りやすい罠は、再現確認もそこそこに「AIに直させる」あるいは「思いつきでコードを書き換える」ことです。

Dan氏はAI(Claude)にバグ修正を依頼したところ、AIは「修正しました」と自信満々に答えましたが、実際にはバグは直っていませんでした。なぜなら、AIは 「バグが起きている状態(Repro)」を正確に認識できていなかった からです。

推測だけで修正を行おうとすると、以下のような状況に陥ります。

  • 直っていないのに直ったと勘違いする
  • 関係ない部分を変更して新たなバグを生む

Step 1: 再現手順(Repro)を確立する

バグ修正の第一歩は、「再現手順(Repro: Reproducing case)」 を定義することです。
これは「テスト」と同義であり、以下の3要素を含みます。

  1. 手順: 何をするか(例:ボタンをクリックする)
  2. 期待される挙動: 本来どうなるべきか(例:下にスクロールする)
  3. 実際の挙動: 今どうなっているか(例:スクロールがガタつく)

再現手順が信頼できる(毎回発生する)ものであればあるほど、デバッグの効率は上がります。

Step 2: 再現手順を「絞り込む」(Narrow the Repro)

バグによっては、目視でしか確認できないもの(例:画面のガタつき)があります。しかし、自動化や効率化のためには、より単純で計測可能な再現手順に置き換えることが有効です。

視覚的なバグを数値的なバグに変換する例

  • 元のRepro: 目視で「ガタつき」を確認する(AIには見えない)。
  • 新しいRepro: ボタンクリック前後の「スクロール位置(座標)」を計測し、差分がなければバグとみなす。

重要:新しいReproの検証

再現手順を変更する際は、「新しい再現手順が、本当に元のバグを捉えているか」 を厳密に確認する必要があります。もし無関係な現象を捉えてしまっていた場合、間違った問題を解くことに時間を費やすことになります。

検証テクニック:
バグを修正する(あるいは関連するコードを無効化する)ことで、新しい再現手順の結果が「成功」に変わるかを確認します。
筆者の例では、ネットワーク呼び出しをコメントアウトすると、スクロール位置が正常に変化することを確認しました。これにより、「スクロール位置の計測」という新しい再現手順が信頼できるものだと証明されました。

Step 3: 余計なものを全て削ぎ落とす(Remove Everything Else)

これこそが本ドキュメントの核となる手法です。
確実な再現手順(Repro)を手に入れたら、バグの原因を特定するために 「バグが再現し続ける限り、コードを削除し続ける」 という作業を行います。

デバッグの基本アルゴリズム

このプロセスは「整礎関係(Well-founded relation)」の概念に似ています。常に「問題の範囲」を小さくし続けることで、必ず終わり(原因の特定)に到達します。

具体的な手順:

  1. Reproを実行し、バグがあることを確認する。
  2. 関連するコードから何かを削除する(コンポーネント、イベントハンドラ、スタイル、インポートなど)。
  3. 再度Reproを実行する。
  4. バグがまだ存在する場合: その削除は「バグに関係ない部分」を削ることに成功した。→ コミットする。
  5. バグが消えてしまった場合: 消した部分に原因(もしくはその一部)が含まれていたことになる。→ リセット(Undo)し、削除する単位を小さくしてやり直す。

よくある間違い:「仮説」をテストしてしまう

多くのエンジニア(およびAI)は、コードを削る過程で「これが原因ではないか?」という仮説 を立て、その仮説を検証するための別個のテストケースを作ろうとします。

しかし、仮説検証アプローチには弱点があります。仮説が外れた場合、振り出しに戻ってしまうことです。
確実なのは、「現在バグが起きているコード」から、バグが消えない範囲で贅肉を削ぎ落としていくことです。このアプローチなら、常に前進し続けることができます。

教訓:
最小の再現コードを作る際、「新しく小さなコードを書く」のではなく、「既存のコードを削って小さくする」方が確実です。

Step 4: 真因の特定(Find the Root Cause)

Step 3を繰り返すと、最終的にこれ以上削れない最小限のコードが残ります。ここまで来れば、バグの原因は分かったも同然ですよね。

筆者の事例では、最終的に「React Routerの特定のコンポーネント内に配置した時だけバグが起きる」ことまで絞り込めました。
結果として、古いバージョンのライブラリ(React Router)にあった「再検証(Revalidation)のたびにスクロール復元が走ってしまう」という既知のバグが原因であることが判明しました。

Flowまとめ

  1. Repro: 信頼できる再現手順(テスト)を作る。
  2. Narrow: 必要であれば、より単純で高速な再現手順に変換し、相関関係を検証する。
  3. Remove: バグが再現し続ける限り、コードを削除し続ける(「仮説」に飛びつかず、確実に範囲を狭める)。
  4. Root Cause: 残った最小限のコードから原因を特定する。

Dan氏曰く、このFlowは数万行の巨大なアプリケーションであっても有効であり、推測で時間を浪費するのを防ぐ唯一の確実な方法とのこと。推測ではなく、論理でバグを追い詰めましょう。

Discussion