Error.stack を使って JavaScript エラーをすばやく診断する
Windows 8 Consumer Preview の IE10 では、Error.stack
がサポートされています。これにより、Web 開発者はバグ (特に、再現が難しいバグ) をこれまでよりすばやく診断して修正することができるようになります。開発者は、今日の近代的なブラウザーの能力を強化する Web プラットフォーム機能によって、すばらしいアプリを構築することができます。私たちは、Windows 8 で Internet Explorer 10 と JavaScript の Metro スタイル アプリの両方をとおしてその能力を明らかにしました。これらのアプリの能力と複雑さが増しているということは、開発者がエラーを処理してバグを診断するために Error.stack
のような優れたツールを必要としているということです。
アプリケーションのデバッグ
JavaScript の構造化エラー処理は、throw
と try/catch
で行われます。このとき、開発者はエラーを宣言し、エラー処理を行うプログラムの一部分に制御フローを渡します。エラーがスローされると、Internet Explorer の JavaScript エンジンである Chakra は、エラーの発生元に導いた呼び出しのチェーンをキャプチャします (コール スタックとも呼ばれます)。スローされたオブジェクトが Error
の場合 (または、プロトタイプ チェーンが Error
に戻る関数の場合)、Chakra はスタック トレース (人が判読できるコール スタック リスト) を作成します。このリストは、Error
オブジェクトでプロパティ stack
として表現されます。stack
には、エラー メッセージ、関数名、および関数のソース ファイルの場所の情報が含まれています。この情報により、開発者はどの関数が呼び出されたかを調べることですばやく不具合を診断し、エラーのあったコードの行を知ることさえできます。たとえば、関数に渡されたパラメーターが null または無効な型であることを示している可能性があります。
(0, 2)
と (12, 10)
の 2 点の距離の計算を試みる簡単なスクリプトの例を見てみましょう。
(function () {
'use strict';
function squareRoot(n) {
if (n < 0)
throw new Error('Cannot take square root of negative number.');
return Math.sqrt(n);
}
function square(n) {
return n * n;
}
function pointDistance(pt1, pt2) {
return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));
}
function sample() {
var pt1 = { x: 0, y: 2 };
var pt2 = { x: 12, y: 10 };
console.log('Distance is: ' + pointDistance(pt1, pt2));
}
try {
sample();
}
catch (e) {
console.log(e.stack);
}
})();
このスクリプトにはバグがあります。コンポーネントの差を二乗するのを忘れています。この結果、入力内容によって、pointDistance
関数が間違った結果を返すだけの場合と、エラーが発生する場合があります。スタック トレースについて理解するため、F12 開発者ツールでエラーを調べてその [スクリプト] タブを見てみましょう。
スタック トレースは、catch
句でコンソールにダンプされます。これはスタックの一番上であるため、Error が squareRoot
関数で発生していることがすぐに明らかになります。問題をデバッグするために、開発者がスタック トレースのかなり深いところまで進む必要はありません。squareRoot
の前提条件に違反しており、1 つ上のスタックを見れば理由は明らかです。squareRoot
の呼び出し内の部分式自体が square
へのパラメーターでなければならないということです。
デバッグ時、stack
プロパティはブレークポイントを設定するコードを特定するのに役立ちます。コール スタックを参照する方法は、他にもある点を覚えておいてください。たとえば、スクリプト デバッガーを "例外キャッチ時にブレーク" モードに設定した場合、デバッガーを使ってコール スタックを調査できることがあります。展開済みアプリケーションの場合、失敗した呼び出しをキャプチャしてサーバーにログ記録するため、問題のあるコードを try/catch
にラップすることを検討できます。この場合、開発者はコール スタックを見て、問題のある領域を隔離できます。
DOM 例外と Error.stack
上で触れたとおり、スローされるオブジェクトは Error
であるか、そのプロトタイプ チェーンをとおして Error
に戻る必要があります。これは意図的な動作です。JavaScript では、あらゆるオブジェクトのスローがサポートされ、プリミティブを例外としてスローすることもできます。これらすべてをキャッチして調べることができますが、エラーまたは診断情報を取得するためだけに設計されたわけではありません。結果として、スロー時には stack
プロパティによってエラーだけが更新されます。
DOM 例外はオブジェクトですが、Error
に戻るプロトタイプ チェーンがないため、stack
プロパティはありません。DOM 操作を実行して JavaScript 互換エラーを明らかにするシナリオの場合、DOM 操作コードを try/catch
ブロック内にラップし、catch
句内で新しい Error
オブジェクトをスローできます。
function causesDomError() {
try {
var div = document.createElement('div');
div.appendChild(div);
} catch (e) {
throw new Error(e.toString());
}
}
ただし、このパターンを使用するかどうかはよく考えてください。このパターンは、ユーティリティ ライブラリ開発に最も適していると思われます。特に、コードの目的が DOM 操作を隠すことなのか、単にタスクを実行することなのかを考えてください。DOM 操作を隠すことが目的の場合、操作をラップして Error
をスローすることが適切な方法と思われます。
パフォーマンス上の考慮事項
スタック トレースの構築は、エラー オブジェクトがスローされたときに始まります。構築するには、現在の実行スタックをウォークする必要があります。特別大きいスタック (場合によっては再帰的なスタック チェーンも) をスキャンしているときにパフォーマンス上の問題が生じないようにするため、IE は既定で上位 10 のスタック フレームのみ収集します。ただし、この設定は静的プロパティ Error.stackTraceLimit
を別の値に設定することで構成可能です。この設定はグローバルなため、エラーをスローする前に変更しないとスタック トレースに対して効果がなくなります。
非同期例外
スタック トレースが非同期コールバック (たとえば、timeout
、interval
、または XMLHttpRequest
) から生成された場合、非同期コールバック (非同期コールバックを生成したコードではなく) がコール スタックの一番下になります。このことから、問題を引き起こしているコードを見つけ出す点で、あることを予想できます。つまり、複数の非同期コールバックに同じコールバック関数を使っている場合、エラーを引き起こしているコールバックを自分で調べて特定するのは難しいかもしれないということです。では、上のサンプルを少し変更して、sample()
を直接呼び出す代わりに、タイムアウト コールバックに置いてみましょう。
(function () {
'use strict';
function squareRoot(n) {
if (n < 0)
throw new Error('Cannot take square root of negative number.');
return Math.sqrt(n);
}
function square(n) {
return n * n;
}
function pointDistance(pt1, pt2) {
return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));
}
function sample() {
var pt1 = { x: 0, y: 2 };
var pt2 = { x: 12, y: 10 };
console.log('Distance is: ' + pointDistance(pt1, pt2));
}
setTimeout(function () {
try {
sample();
}
catch (e) {
console.log(e.stack);
}
}, 2500);
})();
このスニペットを実行すると、少し遅延した後にスタック トレースが生成されます。今回は、スタックの一番下がGlobal Code
ではなく、Anonymous function
になっていることもわかります。実際、これは同じ非同期関数ではなく、setTimeout
に渡されたコールバック関数です。コールバック前後のコンテキストが失われたため、コールバックが呼び出された原因を特定することはできない可能性があります。あるコールバックがさまざまなボタンの click
イベントを処理するように登録されたシナリオを検討している場合、その登録が参照するコールバックを見分けることはできなくなります。もっとも、ほとんどの場合はスタックの一番上が問題のある領域を示していると考えられるため、この制限はたいしたことはありません。
テスト ドライブ デモの確認
Windows 8 Consumer Preview で IE10 を使ってこのテスト ドライブ デモ (英語) を確認してみましょう。コードは eval
のコンテキストで実行でき、エラーが発生した場合は調べることができるようになります。IE10 内でコードを実行している場合は、スタック トレース内のエラーのある行の上にマウス カーソルを置くとコードの行を強調表示することもできます。コードを [Code] 領域に自分で入力するか、一覧の複数のサンプルから選択することができます。コード サンプルの実行時に、Error.stackTraceLimit
値を設定することもできます。
参考資料として、Error.stack
(英語) と stackTraceLimit
(英語) に関する MSDN ドキュメントを調べることもできます。
—Chakra ランタイム担当プログラム マネージャー Rob Paveza