Error.stack を使って JavaScript エラーをすばやく診断する

Windows 8 Consumer Preview の IE10 では、Error.stack がサポートされています。これにより、Web 開発者はバグ (特に、再現が難しいバグ) をこれまでよりすばやく診断して修正することができるようになります。開発者は、今日の近代的なブラウザーの能力を強化する Web プラットフォーム機能によって、すばらしいアプリを構築することができます。私たちは、Windows 8 で Internet Explorer 10 と JavaScript の Metro スタイル アプリの両方をとおしてその能力を明らかにしました。これらのアプリの能力と複雑さが増しているということは、開発者がエラーを処理してバグを診断するために Error.stack のような優れたツールを必要としているということです。

アプリケーションのデバッグ

JavaScript の構造化エラー処理は、throwtry/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 開発者ツールでエラーを調べてその [スクリプト] タブを見てみましょう。

console.log(e.stack) を呼び出すことでログ記録されたスタック トレースを示している F12 開発者ツールのスクリーンショット。ここで、e は try/catch ブロックの catch 句に渡された Error オブジェクトです。

スタック トレースは、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 を別の値に設定することで構成可能です。この設定はグローバルなため、エラーをスローする前に変更しないとスタック トレースに対して効果がなくなります。

非同期例外

スタック トレースが非同期コールバック (たとえば、timeoutinterval、または 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 イベントを処理するように登録されたシナリオを検討している場合、その登録が参照するコールバックを見分けることはできなくなります。もっとも、ほとんどの場合はスタックの一番上が問題のある領域を示していると考えられるため、この制限はたいしたことはありません。

テスト ドライブ デモの確認

テスト ドライブ デモ「Error.stack の確認」(英語) のスクリーンショット

Windows 8 Consumer Preview で IE10 を使ってこのテスト ドライブ デモ (英語) を確認してみましょう。コードは eval のコンテキストで実行でき、エラーが発生した場合は調べることができるようになります。IE10 内でコードを実行している場合は、スタック トレース内のエラーのある行の上にマウス カーソルを置くとコードの行を強調表示することもできます。コードを [Code] 領域に自分で入力するか、一覧の複数のサンプルから選択することができます。コード サンプルの実行時に、Error.stackTraceLimit 値を設定することもできます。

参考資料として、Error.stack (英語) と stackTraceLimit (英語) に関する MSDN ドキュメントを調べることもできます。

—Chakra ランタイム担当プログラム マネージャー Rob Paveza