12周年記念パーティ開催! 2024/5/10(金) 19:00

JavaScriptのデバッグ 第1回 ブレークポイントの使用

Chromeのデベロッパーツールを例に、ブレークポイントの使い方を解説します。プログラムの任意の行を基準に、処理が進む過程をコントロールしながら、関数の実行や、変数の内容などを検証していくことができます。

発行

著者 外村 和仁 フロントエンド・エンジニア
JavaScriptのデバッグ シリーズの記事一覧

デバッグスキルを高める

プログラマに必要とされるスキルはいくつかありますが、その中でもエラーの原因をすばやく突き止めることができる、デバッグのスキルは非常に重要なスキルのひとつです。

デバッグのスキルは経験に依存するところも大きいですが、ツールの使い方を知り、使いこなすことができることも重要です。本シリーズでは、Google Chromeのデベロッパーツール(開発ツール)を利用したJavaScriptのデバッグ手法を解説します。

第1回目はブレークポイントという機能を使い、途中でプログラムの実行を止めることで効率的にデバッグする手法を紹介します。

console.logでのデバッグ

JavaScriptを普段書いている人であれば、console.logを使ったことがないという人はまずいないでしょう。console.logを使うと、変数の途中の状態を出力できたり、特定の行が実行されているかを確認できたりと、開発時には大変便利な機能です。

var $el = $('.foo');

console.log($el); // 要素が取得できているか確認

console.logだけでもある程度のケースはカバーできますが、今回紹介するブレークポイントを使うと、console.logだけではデバッグが難しいケースでも、効率よくデバッグすることができるようになります。

ブレークポイントの設定

ブレークポイントを利用すると、プログラムの実行を任意の場所で一時停止し、停止した場所の変数の状態やコールスタック(関数の呼び出し経路)を確認したり、停止した場所からコードの実行を一つ一つ自分で進めることができます。

次の例を元にブレークポイントの使い方を解説していきます。

function sum(arr) {
  var _sum = arr.reduce(function(a, b) {
    return a + b;
  });

  return _sum;
}

function average(arr) {
  var _sum = sum(arr);
  var _avg = _sum / arr.length;

  return _avg;
}

var avg = average([1, 2, 3, 4, 5]);
console.log(avg);

配列で渡した数値の合計を返すsum関数と、平均値を返すaverage関数があり、average関数は内部でsum関数を利用しています。ここでaverage関数を実行したときの動作をブレークポイントを使って追いかけてみます。

次のサンプルは、[新しくサンプルを開く]をクリックして、別タブで表示。デベロッパーツールのSourcesパネルを開き、左上の[Show Navigator]ボタンをクリックして、デバッグしたいソース(app.js)を表示してください。

ブレークポイントを設定するにはデベロッパーツールのSourcesパネルを使います。Sourcesパネルでブレークポイントを設定したいファイルを開いたら、左側の行番号が書いてある部分をクリックします。まずはaverageの最初の行(10行目)をクリックします。

そうすると左側に青い矢印のようなマークがつきます。これがブレークポイントを設定した状態です。この状態でリロードすると10行目で処理が止まり、次のような状態になります。

この状態では、JavaScriptの処理が青い背景の行の前で完全にストップした状態になります。ブラウザのスクロールなども動きませんし、ページの読込中であれば、このスクリプト以降のHTMLのレンダリングも行われず、ブラウザのページのタブには、読込中のクルクルが出たままの状態になります。

また、デベロッパーツールから設定する方法のほかに、JavaScriptのソースの中にブレークポイントを指定する方法もあります。それにはdebugger文を使います。

function average(arr) {
  debugger;
  var _sum = sum(arr);
  var _avg = _sum / arr.length;

  return _avg;
}

これでデベロッパーツールからブレークポイントを設定したときと同じ動作になります。

処理を進める

ブレークポイントを設定した行の直前で処理が止まった状態から処理を進めるのは、手動で行う必要があります。処理を進めるには右上にあるいくつか矢印のボタンを使います。

重要なのは上記に示したステップオーバー、ステップイン、ステップアウトの3つのボタンです*。それぞれについてどのような動作なのかを解説します。

*注:そのほかのボタン

ステップオーバーの左のボタンは処理の再開/停止を行うボタンで、処理の停止中に押すと、次のブレークポイントまでの処理を一気に進め、次のブレークポイントがない場合は通常の状態に戻ります。ステップアウトの右のボタンはブレークポイントの有効/無効を切り替えるためのボタンです。

ステップオーバー

現在の行(例では10行目)を実行し、次の行に進めます。もし現在の行に関数呼び出しがあったとしても関数へジャンプせずに関数の処理を行い、次の行に進みます。ただし、次の行に進むだけで、その行が実行されるわけではありません*。

*注:青くハイライトされた行

青くハイライトされている行は、その行の先頭で処理が止まっていると考えてください。その行自体は実行されていません。

前述の例で、average関数の1行目var _sum = sum(arr);でステップオーバーを押すと、次のようになります。

sum関数が実行されていますが、sum関数にはジャンプせず、次の行に進んでいることがわかります。

ステップイン

現在の行を実行し、次の行に進めます。もし現在の行に関数呼び出しがあった場合、その関数にジャンプし、呼び出した関数の内部に進めます。

average関数の1行目でステップインを押すと次のようになります。

今度はsum関数の内部にジャンプしたことがわかります。関数呼び出しがない行ではスッテプオーバーとステップインはどちらを押しても同じ結果になります。

ステップアウト

現在の行の呼び出し元の関数が終了するまで実行し、関数を抜けます。

average関数の1行目でステップアウトを押すと次のようになります。

average関数を呼び出していた16行目の処理が終わった状態になり、17行目にジャンプしたことがわかります。

このように、ステップオーバー、ステップイン、ステップアウトをうまく使うことで処理を進めていくのが基本的な使い方です。

変数の状態の確認

ここまではブレークポイントの設定方法と処理を手動で進める方法を解説しましたが、それだけではブレークポイントがなんの役に立つのか、イマイチわからないと思います。ここからがブレークポイントを使ったデバッグの大事なところです。

デベロッパーツールの右側にScope Variablesというパネルがありますが、これを開くと処理が止まっている箇所での変数の値を確認することができます。

_avg_sumなどのローカル変数やthisの値、グローバル変数などが確認できるのがわかると思います。ここには現在の行で参照できる変数がすべて表示されます。

図中の青くハイライトされた行(10行目)はまだ実行されていないので、_sumはまだundefinedです。ではステップオーバーで次の行に処理を進めてみましょう。次のようになります。

sum関数が実行され、配列の数値の合計である15_sumに代入されたことがわかります。このように、1行ごとに変数の値を確認しながら処理を進めることができます。

変数の状態を変更

また、変数の値を処理の途中で書き換えることもできます。試しに以下の状態で配列のひとつ目の数値を1から100に変更してみます。

この状態でステップオーバーすると_sumの値が114になることがわかります。

このように、実際のコードに手を加えることなく、変数の値を変えながらデバッグできるのは非常に便利です。

変数の状態の監視

Scope Variablesは変数の値を確認するのにとても便利ですが、大規模なコードになると、関連する変数やオブジェクトの数が多くなり、ひとつの変数の値を追いかけたいのに、ステップでコードを進めるたびに該当の変数を探すのが大変な場合があります。

そのような場合はWatch Expressionsを使うと便利です。ここに任意の変数を書いておくとその変数の値の現在の状態を表示してくれます。たとえば_avgを設定すると次のようになります。

最初は何も入っていないのでundefinedです。2回ステップオーバーすると計算結果が_avgに代入されるため、Watch Expressionsで設定した箇所に値が表示されます。

たとえば、localStorageに入れているデータがどこかで意図しないデータに書き換わっていて、その原因を突き止めたい場合などに該当のデータを監視しながらステップ実行していくと便利です。

変数名だけでなく任意の式を書けるため、localStorageのデータをJSONで保存しているような場合でも次のように書けば期待通りに出力してくれます。

JSON.parse(localStorage.foo).bar

コールスタック

次に、Call Stackというパネルに注目してみましょう。ここにはどういう関数を経由して現在の行が呼ばれているかという関数のコールスタックが記録されます。

一番上に現在実行されている関数が表示されており、その下に呼ばれた関数が順番に並びます。averageの下には(anonymous function)とありますが、これは無名関数を意味しており、グローバル領域での関数呼び出しはChromeでは無名関数からの呼び出しとなるようです。

試しにステップインしてsum関数に入ってみます。

コールスタックにsumが増えたのがわかります。さらにステップ・インするとreduceメソッドに指定している無名関数に入ります。

一番上に(anonymous function)が増えました。このように、関数内で関数を実行するたびにスタックに積まれていきます。

また、Call Stackパネルの関数名のところをクリックすれば該当の箇所にジャンプします。今回は単純な例なので関数がどのような順番で呼ばれるかは明らかですが、コードが複雑になってくると、どのような順番で呼ばれたかというのはデバッグする上で重要な手がかりとなります*。

*注:スタックをトレースする別の方法

console.trace()を実行すると、実行した時点でのスタックトレースがコンソールに出力されるので、これを使うという方法もあります。

コラム:ブレークポイントを使ってソースを読む

ブレークポイントはデバッグのための機能ですが、ソースを読むという目的にも有効に活用することができます。ある機能がどのような処理をしているか知りたい場合、直接ソースを読むのもいいですが、ブレークポイントを使って途中で処理を止めて、1行ずつ読み進めると効率よくソースを読むことができます。

たとえばjQueryのremoveメソッドがどのような処理になっているかを読むとしたら、次のようにします。

$(function() {
  var $div = $('div');
  debugger;
  $div.remove();
});

上記のコードを実行すると$div.remove()の前で処理が止まるので、removeメソッドにステップインで入って処理の内容を順番に進めていくことができます。また、途中で変数の値を確認しながら読むことができたり、関数の呼び出しがあった場合に、ステップインで自動で関数にジャンプすることができるなど直接ソースを読むより効率的に読むことができます。

まとめ

今回はブレークポイントの基本的な使い方について解説しました。ブレークポイントがどのようなものかわかっていただけたかと思います。

任意の場所にブレークポイントを貼れるだけでも便利ですが、Chromeのデベロッパーツールは、何かイベントが発生した場合に処理を止めるなど、さまざまな場合にブレークポイントを設定することができます。

次回はそのような状況で利用できるブレークポイントの使い方について解説します。