変数ウォッチの全体像
変数ウォッチは、プログラムを一時停止して「いまこの瞬間の変数や式の値」を安全に観測するデバッグ技法です。IDE(IntelliJ IDEA、Eclipse、VS Code など)のブレークポイントで実行を止め、ウォッチリストに変数や式を登録すると、行ごとに値の変化を追えます。思い込みではなく「事実の値」を見ることで、ロジックの誤りや境界条件の取り違えを素早く特定できます。
目的と効果の理解
何のためにウォッチするか
- 状態確認: 変数・フィールド・戻り値が「期待通りか」をその場で検証する。
- 変化の追跡: ステップ操作で前後の差分を見て、どこでズレたかを特定する。
- 仮説検証: ウォッチ式にロジック断片を入れ、意図どおりに評価されるか試す。
ログ出力との使い分け
- ウォッチの利点: コードを汚さずに即座に観測でき、副作用なしで安全。
- ログの利点: 本番での再現・履歴の蓄積に強い。
- 指針: 開発時はウォッチで原因を狭め、確定後にログへ落とすと効率的。
基本操作とセットアップ
ブレークポイントで止める
- 配置: 問題が起きる直前の行にブレークポイントを置く。
- ステップ: ステップイン/オーバー/アウトで流れを追いながら値を見る。
- 条件付き: 「id == ‘X-999’ のときだけ止める」など条件を付けると、大量データでもピンポイントで観測できる。
ウォッチリストに登録する
- 変数:
total,user.name,list.size()のような参照・プロパティ。 - 式:
price * (1 + rate),Objects.equals(a, b),map.get(key)などの評価式。 - 評価: IDEの「Evaluate Expression」でその場のスコープを使って即時評価できる。
重要ポイントの深掘り
副作用のない観測を徹底する
- 安全な式だけを評価:
get()が重い I/O を伴う、next()が状態を進める、remove()が変更する——この類はウォッチで呼ばない。 - toString の落とし穴: 一部の
toString()が内部アクセスや計算を行う場合がある。巨大コレクションのtoString()は避け、サイズと代表要素のみを見る。
コレクション・マップの中身を見るコツ
- サイズと要約:
list.size(),set.contains(x),map.containsKey(k)で輪郭を掴む。 - 代表要素:
list.get(i)を数点ウォッチ、map.get(k)で箇所を絞る。 - 反復中の注意: 反復を進めるメソッド(
iterator.next())はウォッチで呼ばない。現在の要素はループ変数を直接ウォッチ。
条件付きブレークポイントとヒットカウント
- 条件付き:
user != null && user.isActive()などで「特定条件のみ停止」。 - ヒットカウント: 「100回目で止める」など、境界付近の観測に有効。
- フィルタ: スレッド名やクラス単位で止める設定を使うと、並行環境でも狙い撃ちできる。
スレッド・同期の観測
- スレッド別に見る: 現在のスレッド名/IDをウォッチし、意図したスレッドで動作しているか確認。
- ロック中の観測: 共有状態はロック取得中にウォッチ。非同期更新中は値が揺れるため注意。
実例で身につける
例 1: 計算ロジックのズレを特定する
public class PriceCalc {
static int taxed(int subtotal, double rate) {
int tax = (int) Math.round(subtotal * rate);
return subtotal + tax;
}
public static void main(String[] args) {
int subtotal = 999;
double rate = 0.1;
int total = taxed(subtotal, rate);
System.out.println(total);
}
}
Java- ウォッチする式:
subtotal,rate,subtotal * rate,Math.round(subtotal * rate),tax,subtotal + tax。 - 狙い: 小数点の丸め位置が正しいか(合計丸めか税額丸めか)を、行ごとに値で確認する。
例 2: ループ中の境界条件を目視で潰す
public class Traverse {
public static void main(String[] args) {
int[] a = {10, 20, 30};
for (int i = 0; i <= a.length; i++) { // バグ:<=
System.out.println(a[i]);
}
}
}
Java- ブレークポイント:
System.out.println(a[i])に設定。 - ウォッチ:
i,a.length,i < a.lengthの真偽。 - 結果:
i == a.lengthのときに範囲外(IOBE)が起きる事実を確認し、<へ修正。
例 3: マップ照合の漏れを特定する
import java.util.*;
public class Match {
public static void main(String[] args) {
var ids = List.of("A", "B", "C");
var map = Map.of("A", 1, "C", 3);
for (String id : ids) {
Integer v = map.get(id);
if (v != null && v > 0) {
System.out.println(id + ":" + v);
}
}
}
}
Java- ウォッチ:
id,map.containsKey(id),map.get(id),v != nullの判定。 - 気づき: 「B はキーがない」事実が即座に分かる。必要なら
containsKeyとgetの使い分けを見直す。
例 4: 条件付きブレークポイントで特定IDだけ止める
for (String id : ids) {
process(id); // 条件:id.equals("X-999") のときだけ停止
}
Java- ウォッチ: 問題の
idでのみ停止し、process内のstateやcounterを観測。大量データでも効率よく原因を絞れる。
実戦の注意点とベストプラクティス
評価による副作用を避ける
- 原則: ウォッチは参照・算術・比較など「読み取りのみ」に限定。
- 避ける: I/O、ネットワーク、DB 呼び出し、イテレータ前進、ミューテーション(追加・削除)。
観測点を絞る
- 効果的なポイント: 条件分岐の直前、ループの境界、メソッドの入口と出口。
- 少数精鋭: ウォッチ式は「なぜ」を説明できる最小セットに絞る。増やしすぎるとノイズになる。
ウォッチで確信、修正はコードへ
- 型へ落とす: たとえば「null だった」事実を
Objects.requireNonNullや早期リターンのガードへ反映。 - テストに昇華: 観測した境界条件を単体テストにし、再発防止をコード化する。
仕上げのアドバイス(重要部分のまとめ)
変数ウォッチは「いま、ここ」の値を安全に観測して、誤りを事実で突き止めるための最短ルートです。ブレークポイントで止め、ウォッチ式で仮説を検証し、ステップ操作で変化を追う。評価は副作用ゼロに徹し、巨大構造は要約(サイズ・代表要素)で見る。条件付きブレークポイント・ヒットカウントを活用し、並行環境ではスレッドと同期の文脈も観測する。ウォッチで得た事実は、ガード節・修正・テストへ速やかに落とし込む——この型が身につけば、デバッグは短く、正確になります。
