デバッグ
プログラムの不具合を発見し、原因を特定して修正する一連の作業
デバッグとは
デバッグ (Debug) は、プログラムの不具合 (バグ) を発見し、原因を特定して修正する作業である。書くこと自体より時間を取られやすい作業で、その理由は「症状が出た場所」と「原因がある場所」が離れるところにある。null 参照で落ちた行は、その値を null のまま返した関数でも、その関数に空のレスポンスを渡した通信処理でもない。デバッグの実体は修正ではなく、症状から原因までの距離を詰める探索である。
デバッグの基本手順
デバッグは以下の手順で進める。
- バグを再現する (同じ条件で同じエラーを起こす)
- 原因の範囲を絞り込む (どのファイル、どの関数か)
- 仮説を立てる (なぜこの動作になるのか)
- 仮説を検証する (ログ出力、ブレークポイント)
- 修正する
- 再現手順で修正を確認する
この手順で要になるのはステップ 1 の再現である。再現手順がなければ、修正したつもりの変更が本当に効いたのかを確認できず、たまたま症状が出なかっただけの状態を「直った」と誤認する。再現できたら、次に手順を削れるところまで削る。5 画面たどる手順が 1 リクエストに縮まれば、それだけで原因の範囲は劇的に狭まる。
どうしても再現しない場合は、再現を諦めるのではなく再現できる材料を集める側に回る。発生時刻前後のログ、入力値、環境差 (バージョン・タイムゾーン・データ量) を突き合わせ、足りない情報があればログや計測を先に足して次の発生を待つ。「再現しないので様子を見る」で閉じたバグは、条件が揃ったときに同じ形で戻ってくる。
console.log デバッグ
最も手軽な方法は console.log で変数の値を出力することである。
function calculateTotal(items: Item[]) {
console.log("items:", items); // 入力を確認
const subtotal = items.reduce((sum, item) => {
console.log("item.price:", item.price); // 各要素を確認
return sum + item.price;
}, 0);
console.log("subtotal:", subtotal); // 中間結果を確認
return subtotal * 1.1;
}
手軽だが、大量に入れると出力が読みにくくなる。修正後に消し忘れるリスクもあるため、リンターで本番コードへの混入を弾く設定を入れておくとよい。
ブレークポイントより劣った手法というわけではなく、向く場面が違う。1 万回回るループの何回目で壊れるか、非同期処理がどの順で走ったか、実行を止めると再現しなくなるタイミング依存の不具合、デバッガーを接続できない本番寄りの環境。これらは実行を止めずに履歴が残る出力型が優位である。逆に、止めた時点の変数を隅々まで見たい場面や、どこで値が変わったのか見当もつかない場面はブレークポイントが速い。
ブレークポイント
IDE やブラウザの開発者ツールでは、コードの特定の行で実行を一時停止できる。停止中に変数の値を確認し、1 行ずつ実行を進められる。
| 操作 | 意味 |
|---|---|
| Step Over | 現在の行を実行し、次の行へ |
| Step Into | 関数の中に入る |
| Step Out | 現在の関数から抜ける |
| Continue | 次のブレークポイントまで実行 |
これが可能なのは、デバッガーが外からコードを読んでいるからではなく、処理系側がデバッグ用の窓口を持っているからである。Node.js の場合、--inspect を付けて起動すると既定で 127.0.0.1:9229 で待ち受け、プロセスごとに固有の UUID が割り当てられる。デバッガーはその接続先に Chrome DevTools Protocol のクライアントとしてつながり、停止・再開・変数の取得を要求する。IDE の画面はこのやり取りの見た目にすぎず、同じ窓口へ別のクライアントを繋いでもよい。
node --inspect app.js # 起動してすぐ実行が始まる (接続前に走る)
node --inspect-brk app.js # 接続後に 1 行目で停止する
node --inspect-wait app.js # 接続されるまで実行しない
最初から追いたいのに --inspect を使うと、接続作業をしている間に問題の箇所を通過してしまう。起動処理を追う場合は --inspect-brk か --inspect-wait を選ぶ。接続先の一覧は Chrome や Edge で chrome://inspect (edge://inspect) を開くと Remote Target として表示される。ブラウザを使わず node inspect script.js で同梱のコマンドラインデバッガーを使う手もあり、cont / next / step / out が上の表の操作に対応する。
止める場所を指定するだけでなく、条件を付けられることも押さえておきたい。i === 9998 のような条件付きブレークポイントを使えば、1 万回のループで手動の Continue を繰り返す必要はない。式を登録して停止ごとに評価させるウォッチ (コマンドラインデバッガーなら watch('式')、解除は unwatch('式')) は、値がいつ変わるかを追うときに効く。停止せずにメッセージだけ出すログポイントに対応した環境なら、コードを書き換えずに console.log 相当を差し込める。
二分探索デバッグ
バグの原因がわからないとき、コードの中間地点にログを入れて「ここまでは正常か」を確認する。正常なら後半に、異常なら前半にバグがある。この繰り返しで原因を絞り込む。
全体のコード
├── 前半 ← ここまで正常?
│ ├── 前半の前半
│ └── 前半の後半
└── 後半 ← ここで異常?
├── 後半の前半
└── 後半の後半
同じ考え方はコードの範囲ではなく時間軸にも使える。「先週は動いていたのに今日は壊れている」という形のバグは、原因を読んで探すより、正常だった commit と壊れている commit の間を二分探索するほうが速い。git にはこれを行う git bisect があり、公式ドキュメントでも二分探索によって不具合を持ち込んだ commit を特定するコマンドと定義されている。1000 commit の差でも 10 回程度の判定で 1 つに絞れる。判定を自動化できるテストがあるなら git bisect run に任せられるため、テストを 1 本書く手間が探索全体より安くつくことが多い。
ラバーダックデバッグ
コードの動作を声に出して説明する手法である。ゴム製のアヒル (ラバーダック) に向かって説明するという逸話が名前の由来。人に説明しようとすると、自分の理解があいまいな部分に気づき、バグの原因が見えてくることがある。
よくあるバグのパターン
| パターン | 例 | 対策 |
|---|---|---|
| off-by-one | ループが 1 回多い/少ない | 境界値をテスト |
| null 参照 | undefined のプロパティにアクセス | 省略可能連鎖 (?.) や既定値で受ける |
| 型の不一致 | 文字列の "1" + 1 が "11" | 型を明示的に変換 |
| 非同期の順序 | await の付け忘れ | async/await を正しく使う |
| スコープの誤り | 意図しない変数を参照 | 変数のスコープを狭く保つ |
デバッグの心構え
| 心構え | 説明 |
|---|---|
| 思い込みを捨てる | 「ここは正しいはず」が原因であることが多い |
| 1 つずつ変える | 複数箇所を同時に変えると原因がわからなくなる |
| 休憩する | 行き詰まったら離れる。戻ると解決策が見えることがある |
| バージョン管理を活用 | 動いていた状態との差分を確認する |
最後に、原因が分かった時点で終わりにしないことが後の時間を決める。同じ間違いが他の箇所にも書かれていないかを確認し、再現手順をテストとして残す。テストを残さない修正は、次の改修で誰かが同じ条件を踏んだときに何も警告しない。修正の価値は症状を消したことではなく、その症状が二度と黙って戻ってこない状態を作ったところにある。
この記事は役に立ちましたか?
関連用語
エラーメッセージとは - 読み方 / 原因特定 / 設計のベストプラクティス
エラーメッセージはプログラムが問題を検出した際に原因と場所を伝える通知。スタックトレースの読み方 / ユーザー向けメッセージの設計指針を解説
ロギング
アプリケーションの動作を記録し、デバッグ / 監視 / 監査に活用する仕組み
テスト駆動開発
テストを先に書き、テストが通るコードを実装し、リファクタリングするサイクルで開発を進める手法
純粋関数
同じ入力に対して常に同じ出力を返し、副作用を持たない関数
クロージャ
関数が定義時のスコープへの参照を抱えたまま持ち出され、外部から変数を参照できる仕組み
副作用
関数が戻り値以外に外部の状態を変更する操作で、テストやデバッグを困難にする要因
関連する記事
深夜 3 時のデプロイ前に読み返したい 1 ページ
本番デプロイの直前、最終確認のチェックリストとして技術書の特定のページが役立つことがあります。緊張の場面で頼りになる「お守りの 1 ページ」の見つけ方と活用法。
障害対応の夜に思い出す、あの本の 1 ページ
本番障害の緊迫した場面で、過去に読んだ技術書の知識が助けてくれた経験はありませんか。「いつか役立つ」知識が「今この瞬間」に変わる読書の価値を考えます。
本に書いてあることが古くても学べることはある
プログラミングの世界は変化が速く、本の内容がすぐ古くなると言われます。でも、古い本からでも学べることはたくさんあります。何が古くなり、何が古くならないのかを解説します。