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: 問題を正確に把握する
なぜこれが最初か:問題を正確に理解しないと、見当違いの調査をしてしまう
曖昧な問題把握 vs 正確な問題把握:
- ❌「なんか動かない」「エラーが出る」「おかしい」
- ⭕「注文ボタンをクリックすると、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: デバッガを使う
デバッガはブレークポイント、ステップ実行、変数監視を組み合わせて、プログラムの実行を詳しく観察する強力なツールです。
デバッガの活用シーン:
- 複雑な条件分岐:ステップ実行でどの分岐に入るか確認
- ループの挙動:各イテレーションで変数がどう変化するか確認
- 例外発生箇所:例外ブレークポイントで発生時点の状態を確認
- 値の変化:ウォッチ式で変数の変化を監視
基本操作:
| キー | 操作 | 用途 |
|---|---|---|
| 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のオブジェクトをメソッド呼び出しなど処理しようとしたときに発生します。
// 原因: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分悩んだら相談 → 別の視点で気づくことがある
実践チェックリスト
デバッグ時は、以下を意識しましょう。
問題把握
調査
修正
次のステップ
バグを修正したら、コードをより良くするリファクタリングを検討します。