この記事の目次
Step5: デバッグする
動作確認で問題が見つかったら、原因を特定して修正します。デバッグは単なる「バグ探し」ではなく、論理的に原因を絞り込むプロセスです。
なぜデバッグスキルが重要なのか
初心者あるある: 「なんとなくここが怪しい」→ 適当に修正 → 別のバグを生む
デバッグの進め方を間違えると:
- 時間を浪費: 当てずっぽうで何時間も無駄にする
- 二次被害: 原因不明のまま修正して、別のバグを生む
- 再発: 根本原因を理解せず、同じバグを何度も作る
正しいデバッグの考え方:
バグ ≠ 「コードのどこかが間違っている」
バグ = 「期待と実際の差異がある。その原因がどこかにある」
デバッグの基本ステップ
flowchart TD
A[問題を正確に把握] --> B[仮説を立てる]
B --> C[仮説を検証]
C --> D{原因特定?}
D -->|No| B
D -->|Yes| E[修正する]
E --> F[修正を検証]
F --> G{解決?}
G -->|No| B
G -->|Yes| H[完了]
Step1: 問題を正確に把握する
なぜこれが最初か: 問題を正確に理解しないと、見当違いの調査をしてしまう
❌ 曖昧な問題把握
「なんか動かない」
「エラーが出る」
「おかしい」
✅ 正確な問題把握
「注文ボタンをクリックすると、NullPointerExceptionが発生する」
「数量に0を入れると、エラーにならず0円で注文できてしまう」
「商品名が50文字を超えると、画面が真っ白になる」
確認すべきこと:
- 何が起きているか: エラーメッセージ、画面の状態、ログの内容
- 何が期待される動作か: 仕様上、どうなるべきだったか
- 再現手順: どうすればその問題を再現できるか
- 再現条件: 特定のデータ、環境、タイミングでのみ発生するか
Step2: 仮説を立てる
なぜ仮説が必要か: 「どこが原因かもしれない」を絞り込まないと、全コードを見ることになる
問題: 「注文ボタンクリックでNullPointerException」
仮説例:
1. orderオブジェクトがnullのまま処理している
2. order.getItems()がnullを返している
3. データベースから取得した値がnullになっている
仮説の立て方:
- エラーメッセージから推測
- スタックトレースの行番号から推測
- 最近変更した箇所から推測
- 似たようなバグの経験から推測
Step3: 仮説を検証する
検証方法を選ぶ:
| 仮説 | 検証方法 |
|---|---|
| この変数がnullでは? | ログ出力 or デバッガで確認 |
| この条件分岐が間違っている? | ブレークポイントで実際の値を確認 |
| このメソッドが呼ばれていない? | ログ出力で通過を確認 |
| DBの値がおかしい? | SQLで直接データを確認 |
Step4: 原因を特定する
仮説が正しければ原因特定。間違っていれば新しい仮説を立てる。
検証結果:
✅ 仮説1: orderがnull → 確認したらnullではなかった
✅ 仮説2: order.getItems()がnull → 確認したらnullだった!
原因特定:
→ 新規注文の場合、itemsリストが初期化されていない
Step5: 修正して検証
修正したら、必ず以下を確認:
- 元の問題が解決したか
- 他の機能に影響がないか(回帰テスト)
デバッグの具体的なテクニック
テクニック1: ログ出力で追跡
// 処理の流れを追跡
logger.debug("=== 注文処理開始 ===");
logger.debug("入力値: orderId={}, quantity={}", orderId, quantity);
// 中間値を確認
logger.debug("在庫確認結果: hasStock={}", hasStock);
logger.debug("計算結果: total={}", total);
// 分岐の確認
if (hasStock) {
logger.debug("在庫あり: 注文処理に進む");
} else {
logger.debug("在庫なし: エラー処理に進む");
}
ログ出力のコツ:
- 変数の値だけでなく、変数名も出力する
- 処理の開始・終了が分かるようにする
- 条件分岐のどちらに入ったか出力する
テクニック2: デバッガを使う
デバッガの活用シーン
1. 複雑な条件分岐
→ ステップ実行でどの分岐に入るか確認
2. ループの挙動
→ 各イテレーションで変数がどう変化するか確認
3. 例外発生箇所
→ 例外ブレークポイントで発生時点の状態を確認
4. 値の変化
→ ウォッチ式で変数の変化を監視
基本操作:
| キー | 操作 | 用途 |
|---|---|---|
| F5 | 実行継続 | 次のブレークポイントまで実行 |
| F10 | Step Over | 次の行へ(関数の中には入らない) |
| F11 | Step Into | 関数の中に入る |
| Shift+F11 | Step Out | 現在の関数から出る |
テクニック3: 二分探索で絞り込む
なぜ二分探索か: 調査範囲を効率的に半分ずつ減らせる
問題: 100行の処理のどこかでデータがおかしくなる
二分探索:
1. 50行目にログを入れる
→ 50行目時点では正常 → 51-100行が原因
2. 75行目にログを入れる
→ 75行目時点で異常 → 51-75行が原因
3. 63行目にログを入れる
→ 63行目時点では正常 → 64-75行が原因
4. ...
6-7回の確認で原因箇所を特定できる
テクニック4: 最小再現コードを作る
複雑な問題の場合: 問題を再現する最小のコードを作る
// 本番コード: 複雑で原因が分からない
public void complexMethod() {
// 100行のコード...
}
// 最小再現コード: 問題の本質だけ抽出
public void minimalReproduction() {
List<String> list = null;
list.add("test"); // ここでNPE!
}
よくあるバグパターンと対処法
パターン1: NullPointerException
// 原因: nullチェックなしでメソッド呼び出し
order.getItems().size(); // orderまたはitemsがnull
// 対処: nullチェックを追加
if (order != null && order.getItems() != null) {
order.getItems().size();
}
パターン2: 配列の範囲外アクセス
// 原因: インデックスが範囲外
String[] items = new String[3];
items[3] = "test"; // IndexOutOfBoundsException
// 対処: 範囲チェックを追加
if (index >= 0 && index < items.length) {
items[index] = "test";
}
パターン3: 無限ループ
// 原因: ループ終了条件が満たされない
while (count < 10) {
// countが増加していない!
process();
}
// 対処: 終了条件に向かって進んでいるか確認
while (count < 10) {
process();
count++; // 忘れずに増加
}
パターン4: 型変換エラー
// 原因: 文字列を数値に変換できない
int num = Integer.parseInt("abc"); // NumberFormatException
// 対処: 変換前にチェック
if (str.matches("\\d+")) {
int num = Integer.parseInt(str);
}
パターン5: 条件分岐の漏れ
// 原因: 想定外のケースを考慮していない
switch (status) {
case "A": handleA(); break;
case "B": handleB(); break;
// "C"の場合は何も起きない!
}
// 対処: defaultケースを追加
switch (status) {
case "A": handleA(); break;
case "B": handleB(); break;
default:
throw new IllegalStateException("Unknown status: " + status);
}
デバッグで陥りがちな罠
罠1: 思い込みで調査する
❌ 「このコードは絶対正しい」と思い込む
→ 結局そこが原因だった
✅ 「どのコードも疑わしい」と考える
→ フラットな目で全体を見る
罠2: 修正を急ぐ
❌ 原因が分からないまま修正
→ 運良く直ったように見えても、根本原因は残っている
✅ 原因を特定してから修正
→ 再発を防げる
罠3: 一度に複数の修正をする
❌ 3箇所同時に修正
→ どの修正が効果があったか分からない
✅ 1箇所ずつ修正して確認
→ 原因と対策の対応が明確になる
罠4: ひとりで抱え込む
❌ 何時間も一人で悩む
→ 時間の無駄、精神的に消耗
✅ 30分悩んだら相談
→ 別の視点で気づくことがある
実践チェックリスト
デバッグ時は、以下を意識しましょう:
問題把握
- [ ] エラーメッセージを正確に確認した
- [ ] 再現手順を特定した
- [ ] 期待動作と実際の動作の差を明確にした
調査
- [ ] 仮説を立てて検証している
- [ ] ログやデバッガで事実を確認している
- [ ] 思い込みで判断していない
修正
- [ ] 原因を特定してから修正した
- [ ] 修正後に元の問題が解決したか確認した
- [ ] 他の機能に影響がないか確認した
次のステップ
バグを修正したら、コードをより良くするリファクタリングを検討します。