Skip to content
Published on

体系的デバッグ — 推測ではなく推論でバグを捕まえる

Authors

プロローグ — デバッグの大半は推測だ

バグレポートが来る。開発者はコードを開く。「ここが怪しい」と思い、console.log を一行入れる。ビルドし直し、再現し、ログが出なければ別の場所にもう一行入れる。30分後、コードのあちこちにログが散らばり、バグはまだそこにある。

これが現場で起きているデバッグの90%だ。構造のない推測。「これかな?」「あれかな?」。運が良ければ早く見つかる。運が悪ければ一日を溶かす。そして見つけた後でも、なぜそれがバグだったのか正確に説明できない。説明できなければ、同じバグがまた来る。

デバッグは運ではない。方法論だ。良いデバッガ(人間)は頭が良いのではなく、探索空間を体系的に狭める。100個の可能性をランダムに突くのではなく、一回の実験で50個を消す。次に25個、次に12個。これは才能ではなく、訓練可能なスキルだ。

この記事はそのスキルをまとめる。コアループ、再現性をまず確保する方法、科学的方法をバグに適用する方法、二分探索、スタックトレースを正しく読む方法、observability ツール、難しいバグのクラス、行き詰まったときの抜け方、そして AI エージェントとデバッグする方法まで。言語もスタックも問わない — バグがある場所ならどこでも適用できる。

一行でいえば: デバッグとは、仮説を立て、その仮説を最も効率よく反証する実験を設計することだ。コードを睨むことではない。


1章 · コアループ — 再現 → 分離 → 仮説 → 検証 → 修正 → 確認

すべての体系的デバッグは同じループを回す。順序が重要だ。順序を飛ばすと推測に落ちる。

┌─────────────────────────────────────────────────┐
│  1. 再現 (Reproduce)                            │
│     バグをコマンド一つで再び起こせるか?         │
│             ↓                                   │
│  2. 分離 (Isolate)                              │
│     バグが住む領域を半分に減らす                 │
│             ↓                                   │
│  3. 仮説 (Hypothesize)                          │
│     「X が原因だ」 — 検証可能な一文              │
│             ↓                                   │
│  4. 検証 (Test)                                 │
│     仮説を反証する実験を設計して回す             │
│             ↓                                   │
│  5. 修正 (Fix)                                  │
│     根本原因を直す (症状ではなく)                │
│             ↓                                   │
│  6. 確認 (Verify)                               │
│     再現ケースが通るか? 回帰テストは?          │
└─────────────────────────────────────────────────┘
        ↑________________________________│
         仮説が間違っていたら 3 に戻る

各ステップの核心:

ステップ投げる問いよくある誤り
再現「コマンド一つで再び起こせるか?」再現できないまま推測を開始
分離「範囲を半分にしたか?」コード全体を一度に睨む
仮説「検証可能な一文か?」「何か変だ」のような曖昧な仮説
検証「この実験は仮説を反証できるか?」仮説を確認するだけの実験
修正「症状ではなく原因を直したか?」try/catch で症状だけ覆う
確認「再現ケースが通るか?」目視だけで「直ったっぽい」

このループの力は、各ステップが次のステップを小さくすることにある。再現が確実なら分離が速くなる。分離がうまくいくと仮説が狭まる。仮説が狭いと検証が一発で終わる。逆に再現を飛ばすと、残り全部が推測の上に積み上がる。


2章 · 再現性をまず — 決定論的な再現とケースの最小化

再現できないバグはデバッグできない。直したか確認する方法がないからだ。だから最初のステップは常に**「コマンド一つでバグを再び起こす」**ことだ。

決定論的な再現を作る

「たまに起きる」はデバッグ不可能な状態だ。「たまに」を「常に」に変えなければならない。非決定性の原因を一つずつ固定する。

非決定性の原因固定する方法
時刻 / 日付時計を注入して固定値を使う(clock.now() をモック)
乱数シードを固定(seed=42)
並行性 / スケジューリングスレッド数を1に、またはスケジュールを強制
ネットワーク応答応答を録画/再生(VCR、フィクスチャ)
入力データ失敗した正確な入力をファイルに保存
環境変数 / 設定.env を丸ごとキャプチャ
ビルド / 依存バージョンロックファイル固定、コンテナイメージをピン

目標はこんな一行だ:

# このコマンドは常に同じ失敗を出す
SEED=42 TZ=UTC ./repro.sh fixtures/bug-1234.json

この一行を作るのに30分かかっても、その30分はほぼ常に元が取れる。再現コマンドがあれば、残りのすべてのステップが測定可能になる。

ケースの最小化(Minimize)

再現できたら、次は再現ケースを最小に減らすことだ。1000行の入力でバグが出るなら、990行を消してもバグが出るか見る。出るなら、その990行は無関係だ。

これが「delta debugging」のコアアイデアだ。入力を半分に減らし、それでも失敗するならその半分をまた半分に減らす。失敗が消えたら一段階戻す。

入力 1000行 → 失敗 ✗
入力 前 500行 → 失敗 ✗   (後ろ 500行を捨てる)
入力 前 250行 → 通過 ✓   (一段階戻す)
入力 前 375行 → 失敗 ✗
入力 前 312行 → 失敗 ✗
...
入力 3行 → 失敗 ✗   ← 最小再現ケース

3行の再現ケースは1000行のものより圧倒的にデバッグが楽だ。ノイズが除去されたから、残った3行がそのまま手がかりになる。C/C++ コンパイラのバグには creduce、一般テキストには手作業の二分削除、property-based testing ツールには自動 shrink 機能がある。

最小再現ケースを作る過程そのものがデバッグだ。990行を消す間に、どの行を消したときバグが消えるかが見え、それがそのまま原因の位置だ。


3章 · 科学的方法をバグに — 一度に一変数だけ

体系的デバッグの本質は科学的方法だ。観察 → 仮説 → 実験 → 結論。そして科学的方法の最も重要なルール: 一度に一変数だけ変える。

悪いデバッグ vs 良いデバッグ

悪いデバッグ:
  「うーん、キャッシュを切って、ログレベルを上げて、
   タイムアウトを伸ばして、このライブラリのバージョンも変えてみよう。」
  → バグが消える。でも何のせいで消えたかわからない。
  → 4つのうちどれが原因? わからない。戻すとまた出るかもしれない。

良いデバッグ:
  仮説: 「キャッシュが原因だ。」
  実験: キャッシュだけ切る。他は全部そのまま。
  結果 A: バグ消失 → キャッシュが原因(またはキャッシュと相互作用)
  結果 B: バグそのまま → キャッシュは無関係。仮説を棄却、次の仮説へ。

一度に複数の変数を変えると、結果が出てもどの変数のせいかわからない。情報を得られなかった実験だ。一変数だけ変えれば、どんな結果が出ても情報が得られる — その変数が原因か、原因でないか。

反証を狙え

確証バイアス(confirmation bias)はデバッグの敵だ。「X が原因だ」と信じると、X を確認する証拠ばかり探すようになる。良い実験は仮説を反証しようと設計する。

仮説確認する実験(弱い)反証する実験(強い)
「null ポインタが原因」その地点にログを出すその変数が絶対に null にならないようにして — バグは消えるか?
「race condition だ」並行コードを睨む粗いロックを足して — 消えるか? なら race だ
「この PR が壊した」PR の diff を読むその PR を revert して — バグは消えるか?

仮説が生き残れば(反証に失敗)信頼度が上がる。仮説が死ねば次へ進む。どちらにせよ探索空間が狭まる。

何が変わったか?

動いていたものが止まったなら、最も強力な問いは**「何が変わったか?」**だ。コード、データ、設定、依存、インフラ、トラフィック — 健全だったシステムが止まったなら、何かは変わっている。

「昨日は動いたのに今日動かない」チェックリスト:
  □ コード    — git log、最近のデプロイ
  □ 設定      — 環境変数、フィーチャーフラグ、シークレット
  □ データ    — 新しく入ってきたエッジケース入力
  □ 依存      — ロックファイルの変更、自動更新
  □ インフラ  — インスタンス交換、OS パッチ、証明書失効
  □ トラフィック — 量/パターンの変化、新しいクライアント
  □ 時刻      — 月末、深夜0時、DST、うるう年、epoch 境界

二分探索はデバッグで最も強力な単一テクニックだ。N個の候補を log2(N) ステップに減らす。候補が1000個でも10ステップで終わる。二分探索は3つの場所に適用される: コミット履歴入力コードパス

git bisect — コミット履歴の二分探索

「以前は動いたのに今は動かない」なら、どこかのコミットが壊した。git bisect はそのコミットを log2(コミット数) ステップで見つける。500コミットの間でも9回テストすればいい。

# セッション開始
git bisect start
git bisect bad                  # 現在のコミットは壊れている
git bisect good v2.3.0          # このタグでは動いていた

# git が中間コミットをチェックアウトする。テストして判定:
#   $ ./repro.sh && git bisect good   (または)
#   $ ./repro.sh || git bisect bad
# これを ~9回繰り返すと git が犯人を出力する:
#
#   a1b2c3d is the first bad commit
#   Author: ...
#   Date:   ...
#       refactor: switch session store to redis

git bisect reset                # 元のブランチに復帰

鍵は自動化だ。再現コマンドが終了コードを正確に出せば(成功は0、失敗は非0)、人間が手で繰り返す必要はない:

git bisect start HEAD v2.3.0
git bisect run ./repro.sh
# git が自分で全区間を二分探索し、犯人コミットを吐き出す

git bisect run は、2章の決定論的な再現コマンドがなぜそれほど重要なのかを示す箇所だ。再現コマンド一つあれば、500コミットのデバッグがコマンド一行で終わる。

入力の二分探索

3章のケース最小化がそのまま入力の二分探索だ。1000行の入力で半分を消してみる。1万件のレコードのうちどれがパイプラインを壊すか — 半分ずつ分けて入れる。14ステップで見つかる。

コードパスの二分探索

バグがどの関数で出るかわからないとき、コードパスの中間地点に検証を埋める。処理フローが A → B → C → D → E のとき、C 地点で状態を検査する。

A → B → [C: 状態は正常?] → D → E
         ├─ 正常 → バグは C と E の間 (D を検査)
         └─ 壊れ → バグは A と C の間 (B を検査)

「中間地点でデータがまだ無事か?」を問うのだ。無事ならバグは後ろ、壊れていれば前。各検査がコードパスを半分に減らす。これが、やみくもに console.log をばら撒くのと体系的な print デバッグの違いだ。


5章 · スタックトレースとエラーメッセージを正しく読む

スタックトレースはタダで与えられる最も正確な手がかりだ。なのに多くの開発者が一番上の行だけ見て閉じる。スタックトレースには読み方がある。

スタックトレースの解剖

TypeError: Cannot read properties of undefined (reading 'id')   ← (1) 例外の型 + メッセージ
    at getUserName (src/user.js:42:18)                          ← (2) 投げた地点 (最も深いフレーム)
    at formatProfile (src/profile.js:17:9)                      ← (3) 呼び出し元
    at renderPage (src/page.js:88:14)                           ← (4) そのまた呼び出し元
    at processRequest (src/server.js:120:5)                     ← (5) エントリポイント寄り

読む順序:

  1. 例外の型とメッセージ (1)TypeErrorCannot read properties of undefined。何が undefined だったか(reading 'id')まで教えてくれる。メッセージは最後まで読め。
  2. 最も深いフレーム (2)src/user.js:42。例外が実際に投げられた行。90%はこの近くが原因だ。ただし「近く」であって「正確にそこ」ではない — 42行目で落ちても、undefined が入ったのは呼び出し元のせいかもしれない。
  3. コールスタックを下りていく (3, 4, 5)undefined 値がどこで始まったかを追跡する。getUserName に入った引数がすでに undefined だったなら formatProfile を見て、そこでも undefined だったなら renderPage を見る。これは4章のコードパスの二分探索と同じだ。
  4. 自分のコードとライブラリコードを区別するnode_modules/ のフレームは普通スキップしてよい。自分のコードとライブラリの境界フレームが鍵だ。そこでライブラリに不正な値を渡したのだ。

エラーメッセージを最後まで読め

最もよくある誤り: エラーメッセージの最初の行だけ読んで推測を始める。メッセージは普通、答えに近い情報を含んでいる。

メッセージの手がかりよく見落とすもの
ECONNREFUSED 127.0.0.1:5432ポート番号 — どのサービスか教えてくれる
expected 3 arguments but got 2正確な個数 — どの呼び出しかを絞る
... at line 42 column 18列番号 — 同じ行の正確な位置
Caused by: ...(ネストした例外)本当の原因は一番下の Caused by
警告ログ(WARN)エラーより先に出た警告が手がかり

非同期コードではスタックトレースが切れる。async/await は比較的うまく繋いでくれるが、コールバックやイベントベースのコードは「どこから呼ばれたか」がトレースに残らない。こういうときはエントリポイントでエラーを捕まえ、コンテキスト(リクエスト ID、入力)を一緒にログする必要がある。


6章 · デバッグのための observability — ログ、トレース、メトリクス、デバッガ、そして正しい print

ツールは探索空間を覗く窓だ。ツールごとに見せるものが違う。

ツール見せるもの強み弱み
デバッガ(ブレークポイント)一瞬の状態全体変数・コールスタックを自由に探索タイミングを変える、分散・本番には不向き
print / ログ時間に沿った特定の値の変化どこでも動く、タイミング影響が少ない何を出すか事前に決める必要
構造化ログクエリ可能なイベントストリーム事後分析、本番環境設計が必要
分散トレースサービス境界を越えるリクエストの流れ「どのサービスが遅い/壊れているか」計装が必要
メトリクス時間に沿った集計(レイテンシ・エラー率)トレンド・異常検知個別ケースは見えない
コアダンプ / プロファイラクラッシュの瞬間、または資源使用メモリ・パフォーマンスのバグ分析に専門性が要る

デバッガをまず試せ

print デバッグに落ちる前に、デバッガをアタッチできるかをまず問え。デバッガは一つのブレークポイントでその瞬間のすべての変数を見せる。print は事前に決めた変数だけを見せる。ローカルで再現するバグなら、デバッガはほぼ常に速い。条件付きブレークポイント(i == 9999 のときだけ止まる)、ウォッチ式、コールスタックの探索 — これを使わないのは損だ。

デバッガを使えない状況(本番、分散、タイミングに敏感)では print が答えだ。ただし4章で見たとおり体系的に:

悪い print デバッグ:
  怪しい場所ごとに log("here1"), log("here2") を乱射
  → ログ爆弾。何を探しているか不明。

良い print デバッグ:
  - 仮説をまず立てる: 「C 地点で user.id がすでに null だ」
  - その仮説を検証する値だけ出す: log("at C, user.id =", user?.id)
  - コードパスの中間地点から(二分探索)
  - 識別可能なタグを付ける: log("[bug-1234] at C:", ...)
  - 終わったら全部消す(またはログレベルに閉じ込める)

print デバッグは恥ではない。無計画な print が恥だ。仮説を検証する print は正当な実験だ。

本番環境: ログ・トレース・メトリクスの協業

本番のバグは普通、三つを一緒に使う。メトリクスで「いつからエラー率が上がったか」を見つけ(時間範囲を狭める)、トレースで「どのサービスで壊れるか」を見つけ(空間を狭める)、ログで「そのサービスの中で正確にどの値のせいか」を見つける(原因を指す)。広く始めて狭める — 1章の分離ステップと同じ構造だ。


7章 · 難しいバグのクラス — heisenbug、race、メモリ、「自分の環境では動く」

あるバグはコアループを素直に適用しにくい。再現できないか、観察すると消えるからだ。クラスごとに戦略が違う。

Heisenbug — 観察すると消えるバグ

ブレークポイントを置いたりログを足したりすると消えるバグ。普通はタイミング初期化されていないメモリが原因だ。print 一行がタイミングを変えるか、デバッグビルドがメモリを0に初期化する。

戦略: 観察ツールがタイミングを変えないようにする。非同期・バッファリングされたログ、または事後のコアダンプ。デバッグビルドではなくリリースビルドで再現する。そして「観察すると消える」という事実そのものが手がかりだ — ほぼ常にタイミング/メモリ初期化の問題だ。

Race condition — 順序に依存するバグ

二つのスレッド/プロセスの実行順序によって結果が分かれるバグ。1000回に1回出る。

戦略:

  • 再現率を上げる。 怪しい区間に sleep を入れて race window を人為的に広げる。1000回に1回が10回に1回になればデバッグ可能になる。
  • 反証実験。 怪しい区間に粗いロックを掛けてみる。バグが消えれば race だ(3章)。
  • ツール。 ThreadSanitizer、Go の -race、Java の並行性検証ツールは data race を静的・動的に捕まえる。

メモリのバグ — リーク、破損、use-after-free

戦略: AddressSanitizer/Valgrind で破損と use-after-free を捕まえる。リークはヒープスナップショットを二時点で取り、差分(diff)を見る — 何が積み上がり続けるかがそのまま手がかりだ。メモリのバグは症状が原因から遠く離れて現れるのが特徴だ。破損したメモリを読む場所ではなく書いた場所が原因だ。

「自分の環境では動く」

環境差のバグ。自分の環境と壊れる環境の差分リストを作る。

環境 diff チェックリスト:
  □ OS / アーキテクチャ (arm64 vs x86_64、改行 CRLF/LF)
  □ 言語 / ランタイムのバージョン
  □ 依存のバージョン (ロックファイルを本当にコミットしたか?)
  □ 環境変数 / 設定ファイル
  □ ロケール / タイムゾーン / エンコーディング
  □ ファイルシステム (大文字小文字を区別するか)
  □ データ (ローカル DB vs 本番 DB の実データ)
  □ 権限 / ネットワーク / ファイアウォール

根本的な解決は環境をコード化することだ — コンテナ、ロックファイル、IaC。「差」を0に近づければ、このバグのクラス自体が消える。

分散・タイミングのバグ

複数のサービスにまたがるバグ。単一のスタックトレースでは見えない。鍵は相関 ID(correlation ID) — リクエストが始まるとき ID を発行し、すべてのサービスのログにその ID を一緒に残す。すると分散トレースで一つのリクエストの全体の旅を再構成できる。clock skew にも注意 — サービス間のタイムスタンプを比較するとき数秒ずれることがある。


8章 · 本当に行き詰まったとき — ラバーダック、休憩、前提を疑う

方法論に従っても行き詰まることはある。行き詰まりは普通、間違った前提の上で探索しているというサインだ。

ラバーダックデバッグ

問題を最初から最後まで、一行ずつ、声に出して説明する。ゴムのアヒルにでも、同僚にでも、空のチャット欄にでも。説明しているうちに「あれ… 待って、これなんでこうなってる?」という地点が出てくる。頭の中で飛ばしていた前提を口に出すと、それが露わになる。

休憩を取れ

2時間行き詰まっているなら、もっと睨んでも解けない。散歩、睡眠、別の作業。無意識が背景で探索空間を再整理する。「シャワーを浴びていて答えが浮かんだ」はクリシェではなく認知科学だ。行き詰まったときの休憩は怠けではなく戦略だ。

前提を疑え

行き詰まったときの最も強力な行動: これまで事実だと信じていたものを一つずつ疑う。

「確実だと思っていたもの」を検証せよ:
  - 「この関数は確かに呼ばれる」 → 本当に? ログで確認したか?
  - 「この値は絶対に null ではない」 → 本当に? assert を掛けたか?
  - 「自分が直したコードがデプロイされた」 → 本当に? ビルドハッシュを確認したか?
  - 「DB には正しいデータがある」 → 本当に? 直接クエリしたか?
  - 「この設定ファイルが読まれる」 → 本当に? どのパスを読むか見たか?
  - 「ライブラリはドキュメントどおりに動く」 → 本当に? ソースを見たか?

検証していない前提は前提ではなく推測だ。「確かに〜だろう」と言った瞬間、それがまさに検証すべき対象だ。

バグは君が思う場所にない

長く行き詰まっているなら、探索範囲そのものが間違っている可能性が高い。一週間アプリケーションコードを見たのに、実は原因は設定ファイルだったり、ライブラリのバージョンだったり、データだったり、インフラだったりする。一つの領域を十分に探したのに無いなら — その領域ではない。範囲を広げろ。1章のコアループに戻って、「再現」からやり直す。しばしば再現ステップを雑にやっていたことが露わになる。


9章 · AI エージェントとデバッグ — エージェントにも推測させるな

AI コーディングエージェントは強力なデバッグのパートナーだが、使い方を誤ると人間より速く推測する。コア原則は同じだ: エージェントに推論する材料を与え、推測できないようにする。

エージェントに与えるべきもの

エージェントはコンテキストが良いほど正確だ。デバッグを依頼するとき、次を一緒に渡す:

与えるものなぜ
再現コマンド(2章)エージェントが自分の仮説を直接検証できるように
正確なエラー / スタックトレース(5章)「エラーが出る」ではなく全文を
何が変わったか(3章)最近の diff、デプロイ — 探索範囲を狭める
すでに試して除外したもの同じ仮説を繰り返さないように
期待する動作 vs 実際の動作バグの定義を明確に

再現コマンドを与えるのが最も重要だ。すると、エージェントは「これが原因っぽい」ではなく「仮説を立てて → repro コマンドで検証して → 通過/失敗を報告」というコアループを自分で回せる。

エージェントに推測させるな

エージェントが陥るアンチパターンは人間とまったく同じだ — 一度に複数の場所を変え、仮説なしにコードを修正し、症状を try/catch で覆う。塞がねばならない:

エージェントのデバッグを管理する方法:
  - 「推測するな。まず仮説を述べてから検証しろ」と指示
  - コアループを明示的に要求: 再現 → 分離 → 仮説 → 検証
  - 一度に一変数だけ — 「複数を同時に変えるな」
  - 修正前に「根本原因が何か」を説明させる
  - 修正後に再現コマンドで確認し、結果を見せてもらう
  - エージェントの仮説を人間がレビュー — もっともらしく見えても検証前は推測

エージェントの利点は、疲れずに二分探索を回し、膨大なログを速く読み、git bisect を自動で回せることだ。弱点は、もっともらしい仮説を自信を持って述べることだ。だから人間の役割は方法論を強制すること — エージェントにコアループを従わせ、検証されていない仮説を事実として受け入れないことだ。

人間でもエージェントでも、ルールは同じだ: 検証されていない仮説は推測だ。推測でコードを直さない。


エピローグ — チェックリストとアンチパターン

体系的デバッグは才能ではなく、訓練可能な手順だ。コアループに従い、一度に一変数だけ変え、二分探索で探索空間を狭め、検証されていない前提を疑う。推測ではなく推論だ。

デバッグチェックリスト

  1. 再現したか? — コマンド一つでバグを確実に起こせるか?(できなければここで停止)
  2. 最小化したか? — 再現ケースから無関係な部分を全部除去したか?
  3. 何が変わったか? — コード/設定/データ/依存/インフラのうちどれが?
  4. 仮説は一文か? — 「X が原因だ」 — 検証可能な形か?
  5. 一度に一変数か? — この実験で変えるのは一つだけか?
  6. 反証を狙っているか? — この実験は仮説を殺せるか?
  7. 二分探索を使えるか? — git bisect / 入力 / コードパスのどこに?
  8. エラーメッセージを最後まで読んだか? — スタックトレース全体を?
  9. 正しいツールか? — デバッガをまず試したか?
  10. 根本原因を直したか? — 症状ではなく原因を? なぜバグだったか説明できるか?
  11. 確認したか? — 再現ケースが今は通るか? 回帰テストを追加したか?
  12. 行き詰まったら — ラバーダック、休憩、前提を疑う、範囲を広げる。

アンチパターン

アンチパターンなぜ悪いか代わりに
再現なしに推測を開始直したか確認できない再現コマンドをまず作る
一度に複数の変数を変える何が原因かわからない一度に一つだけ
スタックトレースの最初の行だけ読む本当の手がかりを見落とす最後まで、Caused by まで
console.log を乱射ログ爆弾、無計画仮説を検証する値だけ
症状を try/catch で覆うバグはそのまま、より深く隠れる根本原因を直す
確証バイアス(仮説の確認だけ)間違った仮説を捨てられない反証する実験を設計
検証されていない前提の上で探索間違った場所を永遠に掘る「確かに〜」を全部検証
目視して「直ったっぽい」回帰がまた来る再現ケースで確認 + 回帰テスト
行き詰まったのに睨み続ける同じ推測を繰り返す休憩 / ラバーダック / 範囲を広げる
エージェントに推測を委ねる速い推測でも推測だ再現・ログを与えてループを強制

次回予告

次回は**「回帰テストの設計 — バグに二度会わない方法」**だ。この記事がバグを見つける方法なら、次回は同じバグに再び会わない方法だ。再現ケースを永続的なテストに固める方法、どのバグが回帰テストを受けるべきか、テストが本当にそのバグを捕まえるか検証する方法、そして flaky な回帰テストを作らない方法を扱う。バグをうまく捕まえることと同じくらい重要なのが、捕まえたバグを閉じ込めることだ。