JavaScriptのデバッグのコツと技

以前の記事で、Webアプリケーションのデバッグの仕組みについて触れました。今回は実践的なJavaScriptのデバッグについて掘り下げていきたいと思います。

ブラウザデベロッパツール

私の個人的なお気に入りはChromeデベロッパツールです。SafariやFirefoxはChromeほどの高水準に達していません。しかし、徐々に改善されてきています。FirefoxにはFirebugと改良されたFirefoxデベロッパツールが組み合わされた機能が備わっています。もし、Firefoxチームがビルトインされているデベロッパツールの改良の中で素晴らしい仕事をし続けたとしたら、Firebugはいつか、すたれるかもしれません。

個人的な好みにかかわらず、ターゲットとするあらゆるブラウザで、全てのコードのテストやデバッグができるようにすべきです。”あらゆるブラウザ”には、かの有名なInternet Explorer 8も入ってくるかもしれませんね。

まずは自分の選択したデベロッパツールに慣れておきましょう。IDE(統合開発環境)あるいはサードパーティソフトウェアから追加のデバッグに関するサポートを得られるかもしれません。

1つのデベロッパツールの基礎知識を習得しておけば、他のツールにその知識を適応することができます。実際、私がデバッグの基礎について学んだのは1990年代のことで、BorlandのC言語開発環境でした。ブレークポイント、条件付きブレークポイント、そしてウォッチは、一番最近のChromeデベロッパツールとまさに同じものでした。私がJavaで初めて”例外”に出くわしたのは、西暦2000年近くのことです。スタックトレースのコンセプトはまだ有効で、JavaScriptの専門用語ではErrorオブジェクトと呼び名は変わったものの、以前と同じくスタックトレースとして使用することができます。

いくつかの知識はフロントエンド開発固有のものです。以下がその例です。

  • DOMインスペクション
  • DOMブレークポイント
  • デバッグイベント
  • メモリリーク解析

ブレークポイント

debuggerステートメントを用いて、ソースコードにブレークポイントを設定することが可能です。いったんdebuggerステートメントに到達すれば、実行は停止します。実行中のスコープのコンテキストはコンソールでローカル変数、グローバル変数と共に表示されます。変数の値は、マウスを動かしてカーソルを当てることで分かります。

コード内においては、条件付きブレークポイントも作成することが可能です。

if ( condition ) {
    debugger;
}

ブレークポイントと条件付きブレークポイントは、あなたの選んだデベロッパツールでも同様に挿入することができます。Chromeデベロッパツールでは、Sourcesパネルの行番号を左クリックすることで、ブレークポイントを設定することができます。ブレークポイントを右クリックし、”Edit Breakpoint(ブレークポイントを編集する)”を選択すれば、条件を付け加えることができます。

ノードの変更時にブレークポイントを設定する

使えないコードのデバッグを行う場合には、実行中になぜDOMノードが編集されたか調べることになるでしょう。Chromeデベロッパツールには、要素のツリーでノードの変更を検知してくれる便利なブレークポイントが備わっています。

Elementsパネルでは、コンテキストのメニューから要素を右クリックし、”Break on…(…の場合、ブレークポイントを設定する)”を選択します。

Break on Node Change
ノードの変更時にブレークポイントを設定する。

実行可能なDOMブレークポイント

  • 選択したノードのサブツリー内での変更
  • 選択したノードの属性の変更
  • 選択したノードの削除

参照型でログをとるのを避ける

オブジェクトや配列のログを取る時、ログ対象のリファンレスの中にあるプリミティブ型の値は変わるかもしれません。参照型を見る時、覚えておいてほしいのは、ロギングと観測の間に実行されたコードは、観測値に影響を及ぼすかもしれないということです。

例えば、以下のコードをChromeデベロッパツールで実行してみましょう。

var wallets = [{ amount: 0 }];

setInterval( function() { 
    console.log( wallets, wallets[0], wallets[0].amount ); 
    wallets[0].amount += 100; 
}, 1000 );

ログされた属性の2つ目と3つ目は正しい値を反映していますが、最初の属性のObjectのリファレンスは信頼できない値になっています。amountの領域にある値は、デベロッパツールで属性を表示しようとした時点で割り当てられます。この値は、同じ問い合わせを何度閉じたり開けたりするかに関わらず、変動しません。

Logging Reference Types
参照型へのロギング

常に、何にロギングしているかを知っておきましょう。プリミティブ型のログでも、ブレークポイントと併せてウォッチ式を使い、非同期的なコードの場合は、参照型へのロギングを避けましょう。

表形式のログ

デベロッパツールの中には、コンソール上でオブジェクト配列のロギングをするためにconsole.tableが使えるものもあります。

Chromeデベロッパツールで、次のような行を実行してみましょう。

console.table(
    [
        { 
            id: 1, 
            name: 'John', 
            address: 'Bay street 1'
        }, 
        {
            id: 2, 
            name: 'Jack', 
            address: 'Valley road 2.'
        }, 
        {
            id: 3, 
            name: 'Jim', 
            address: 'Hill street 3.' 
        }
    ] );

こうすると、見やすい表が作られて、全てのプリミティブ型がすぐに表示されます。これらの値は、ロギングの時点での状態を反映しています。複合型は、型を表示しながらログされます。これらのコンテンツは閲覧できません。それゆえ、console.tableは、プリミティブ値を持つオブジェクト配列の2次元のデータ構造のみ表示するべきです。

XHRブレークポイント

時々、間違いのあるAjaxリクエストに出くわすことがあります。もしもあなたが、このリクエストを送信したコードをすぐに特定できないのであれば、XHRブレークポイントが時間を節約してくれます。特定のタイプのAjaxリクエストが送信されると、XHRブレークポイントはコードの実行を停止させ、そのリクエストを送信しているコードセグメントにユーザを案内してくれます。

ChromeデベロッパツールのSourceタブでは、定義されたブレークポイントの型の1つがXHRブレークポイントです。+のアイコンをクリックすることで、URLの一部を入力することができます。このようなURLの断片がAjaxリクエストのURLに現れると、JavaScriptのコードはいつでも停止します。

イベントリスナーのブレークポイント

あらゆるタイプのイベントは、Chromeデベロッパツールによって認識され、ユーザはキーを押したりマウスをクリックしたりすれば起こったことをデバッグできます。

例外での中断

Chromeデベロッパツールは、例外を投げることでJavaScriptのコードの実行を中断することができます。これにより、Errorオブジェクトが作られた時、あなたのアプリケーションの状態を観測することが可能になります。

Pause on Exceptions
例外での中断

Snippets (スニペット)

Sourcesタブの左パネルには、Snippetsというサブタブがありますが、これはコードのデバッグに役立つコードスニペットを格納する目的で使用します。

デバッグをするのにコンソールを使い続けていて、何度も同じコードを記述しているのであれば、デバッガのスニペットにコードをまとめておくと良いでしょう。そうすることで、あなたが行ったデバッグ手法を同僚に送ることも可能になります。

関数を実行する前にブレークポイントを挿入するといったような、基本的なデバッギングのスニペットについて、Paul Irishがいくつか情報を公開しています。これらのスニペットを調べてみたり、Web上で他のスニペットを検索してみたりするのもあなたの為になるでしょう。

関数の前にブレークポイントを挿入する

関数への参照にアクセスすることができるのであれば、関数が呼び出される直前に実行を停止するブレークポイントを挿入することもできます。デバッグしたい関数をfとした場合、debug(f)ステートメントでブレークポイントを挿入することができます。

ミニファイされたコードを元に戻す

できる限りソースマップを使用しましょう。場合によっては、ソースマップが本番環境にデプロイしないことがありますが、そもそも本番環境でデバッグすべきではないので、これは問題になりません。

ソースマップが使えない場合の最終手段としては、Chromeデベロッパツールにある Sourcesタブ内のPretty Printボタンを使用します。Pretty Printボタン{}は、ソースコードが表示される画面のテキスト領域の下にあります。この機能では、ソースコードの整形や行番号を変更することができ、デバッグを容易にしたり、より効果的なスタックトレースを行ったりすることができます。

Pretty Printは最終的な手段として使用してください。コード名が解釈しづらいという意味では、醜いコードは何をしても醜いのです。

DOM要素に対するコンソールのブックマーク

ChromeデベロッパツールとFirebugの両方で、Elements(Chrome)タブまたは、HTML(Firebug)タブで最後にクリックしたDOM要素に対して、ブックマークをすることができます。ABCの順に要素を選択した場合、以下のような結果を返します。

  • $0Cを返す
  • $1Bを返す
  • $2Aを返す

要素Dを選択した場合は、$0$1$2$3は、それぞれ、DCBAのDOM要素を持つことになります。

コールスタックへのアクセス

console.traceコマンドは、行番号を含めたスタックトレースのログを取ります。

var f = function() { g(); } 
var g = function() { h(); }
var h = function() { console.trace('trace in h'); }

f();

Call Stackは、ChromeデベロッパツールのSourcesタブにある、Watch Expressionの下に表示されます。

監視

しばしば、パフォーマンス監視ツールが役に立つことがあります。これらは、一般的なリーク排除ツールとして機能し、Webサイト内で最適化されうる箇所を検知します。ただし、製品を理解する機能は持ち合わせていないので、得られるアドバイスのうちいくつかは除外することになるかもしれません。大抵の場合、監視ツールは著しく最適化可能と思われる妥当な領域を指摘してきます。

監視ツールの例:

  • ChromeデベロッパツールのAuditタブ
  • YSlow

習うより慣れよ

いくつかのデバッグ手法は、あなたにとって馴染みのあるものかもしれませんが、他の手法を試してみたら、それらが時間削減になるかもしれません。今回紹介したテクニックを実行しようとお考えなのであれば、数週間後に再度この記事を読んでみてください。その数週間の間に、あなたの視点が変化していることに驚くと思いますよ。

この記事を気に入っていただき、他の人にも勧めていただけるようであれば、ぜひ、Twitterに投稿してください。