こんにちは、かつコーチです。
前回はブラウザ互換性対応の考え方を解説しました。
そして今回が、JavaScript基礎シリーズの最終回です。
最後のテーマは「デバッグ」、つまり「コードがなぜ思った通りに動かないのかを調べる技術」です。
エラーの読み方、undefinedとnullの違い、非同期処理の罠……ここまで学んできた知識は、すべてこの「デバッグ力」につながっています。
今回は、私が実務でも日常的に使っているデバッグの手法を一通りまとめて紹介します。
デバッグの基本姿勢
まず「何が起きているか」を切り分ける
デバッグでいちばん大事なのは、いきなり原因を推測してコードを書き換えないことです。
「たぶんここが悪いんだろう」と思って修正しても、実際には別の場所が原因で、余計に混乱が深まることがよくあります。
まずは「どの行まで正常に動いていて、どこから想定外の動きになっているか」を1つずつ確認していく、という地道な切り分けが結局いちばんの近道です。
再現条件を固定する
デバッグを始める前に、「どういう操作をすると問題が起きるか」を明確にしておきましょう。
「ボタンを2回連続でクリックしたときだけ起きる」「特定のユーザーIDのときだけ起きる」など、再現条件が分かっているだけで調査範囲は大きく絞り込めます。
console.logを使ったデバッグ
基本の使い方
一番手軽で、実務でも頻繁に使うのがconsole.logです。
function calculateTotal(items) {
console.log('items:', items);
const total = items.reduce((sum, item) => sum + item.price, 0);
console.log('total:', total);
return total;
}
処理の前後に値を出力しておくことで、「想定していた値が実際に入っているか」を目で確認できます。
ラベルを付けて出力元を分かりやすくする
console.logを複数箇所に置くと、どの出力がどこのものか分からなくなりがちです。
❌ Before:ラベルなしで出力する
console.log(userId);
console.log(cartItems);
console.log(total);
これでは、後からコンソールを見返したときに「このログはどこのconsole.logから出たものか」がすぐには分かりません。
✅ After:文字列ラベルを付けて出力する
console.log('userId:', userId);
console.log('cartItems:', cartItems);
console.log('total:', total);
'ラベル名:'を先頭に付けるだけで、コンソールを見返したときに一目で何の値か判別できるようになります。
複数のログが並ぶ実務のコードでは、この一手間があるかないかでデバッグの速度がかなり変わってきます。
console.tableで配列・オブジェクトを見やすくする
配列やオブジェクトの中身を確認するときは、console.tableを使うと表形式で見やすく表示できます。
const users = [
{ id: 1, name: 'かつコーチ', age: 30 },
{ id: 2, name: 'たなか', age: 25 },
];
console.table(users);
console.logだと折りたたまれた状態で表示されがちな配列オブジェクトも、console.tableなら列ごとに整理された表として一覧できます。
ブレークポイントを使ったデバッグ
かつコーチが実際にハマった話
私が初心者の頃、しばらくconsole.logだけでデバッグをしていて、あるとき「途中の状態を1行ずつじっくり確認したい」という場面に出会いました。
ループの中で条件によって値がおかしくなるバグだったのですが、console.logをループの中に入れると大量のログが流れてしまい、目当ての1回分をログの海から探し出すのに苦労しました。
そこで先輩に「ブレークポイントを使えばいいよ」と教えてもらい、初めてデベロッパーツールの「Sources」タブをまともに触ったのを覚えています。
ブレークポイントの設定方法
Chromeのデベロッパーツールを開き、「Sources」タブで対象のJavaScriptファイルを開きます。
止めたい行の行番号をクリックすると、その行にブレークポイントが設定され、その行に処理が到達した時点でコードの実行が一時停止します。
function calculateDiscount(price, rate) {
const discount = price * rate; // ← この行にブレークポイントを置く
return price - discount;
}
一時停止した状態では、その時点での変数の値をマウスオーバーで確認できたり、コンソールから直接変数を操作したりできます。
console.logをいちいち書いて消してを繰り返すより、複雑な条件分岐やループの中身を調べたいときは、ブレークポイントの方が圧倒的に効率的です。
デバッガのステップ実行を使いこなす
ブレークポイントで止めた後は、次のボタンで1行ずつ処理を進めながら確認できます。
- Step over:次の行に進む(関数の中には入らない)
- Step into:呼び出している関数の中に入って1行ずつ確認する
- Step out:今いる関数から抜けて、呼び出し元に戻る
ループの中で値が想定外になるバグを追うときは、Step overで1周ずつ進めながら、変数の値がどのタイミングでおかしくなるかを目で追っていくのが基本の流れです。
debugger文でコードから一時停止させる
コード中にdebugger;という文を書いておくと、デベロッパーツールが開いている状態でその行に到達した瞬間に自動的に処理が一時停止します。
function processOrder(order) {
if (order.total < 0) {
debugger; // 想定外の値になった瞬間に自動で止まる
}
return order.total * 1.1;
}
「毎回手動でブレークポイントを設定するのが面倒」「特定の条件のときだけ止めたい」という場合に便利な方法です。
本番コードに残したままデプロイしないよう、デバッグが終わったら削除するのを忘れないようにしましょう。
ネットワーク・DOM周りのデバッグ
Networkタブで通信内容を確認する
APIとの通信がうまくいかないときは、デベロッパーツールの「Network」タブを確認します。
リクエストのステータスコード(200、404、500など)や、送信しているデータ、返ってきたレスポンスの中身を1つずつ確認できます。
「JavaScriptのコードは合っているはずなのにデータが表示されない」という場合、実はAPI側がエラーを返していた、というオチも実務では珍しくありません。
Elementsタブで実際のDOM状態を確認する
「CSSで指定したはずのクラスが反映されない」「JavaScriptで書き換えたはずの要素が変わっていない」というときは、「Elements」タブで実際のDOM構造を確認します。
コード上の記述と、ブラウザが実際に描画しているDOMの状態が一致しているとは限らないため、想定通りのクラスや値が入っているかを実際の画面で確認する習慣をつけておきましょう。
まとめ
この記事のポイント
- デバッグはいきなり推測で直さず、「どこまで正常か」を切り分けることから始める
console.logにはラベルを付け、配列・オブジェクトはconsole.tableで見やすく確認する- ループや複雑な条件分岐は、ブレークポイントとステップ実行でじっくり追うのが効率的
debugger文で特定の条件のときだけ処理を一時停止できる- API通信の不具合はNetworkタブ、表示のズレはElementsタブで実際の状態を確認する
次のステップへ
JavaScript基礎編、65記事お疲れさまでした。
とはから始まり、変数、条件分岐、配列、オブジェクト、this、DOM操作、イベント、ハンバーガーメニューのような実践的なUI実装、そして非同期処理やAPI通信まで、かなり長い道のりを一緒に歩んできました。
正直なところ、私自身も学び始めたころはthisの挙動や非同期処理でかなり苦しんだので、ここまで読み進めてきた皆さんは本当によく頑張ったと思います。
さて、次のシリーズでは「TypeScript編」に進みます。
ここまで書いてきたJavaScriptに「型」という考え方を足すことで、undefinedやnullのうっかりミスをコードを書いている最中に検知できるようになったり、チーム開発での安全性がぐっと高まったりします。
今回このシリーズで扱った罠の多くは、実はTypeScriptを使うことで未然に防げるものも多いので、答え合わせのような感覚で読んでもらえるはずです。
そのあとは、いよいよReact・Vue.jsという人気フレームワークにも踏み込んでいく予定です。
今まで素のJavaScriptで手間をかけて実装してきたDOM操作やイベント処理が、フレームワークを使うとどれだけ楽になるかを実感してもらえると思います。
公開までもうしばらくお待ちください。
引き続き、かつコーチと一緒に学んでいきましょう。
