デバッグの課題
デバッグにおける最大の課題とは何でしょうか?その中の一つは、エラーの発生源を特定することです。
次のようなシナリオを想像してみてください:
const func = () => {
doSth('A');
doSth('B');
};
func
がエラーをスローしたとき、そのエラーがどのステップで発生したのかをどのように特定しますか?doSth('A')
に起因するのか、doSth('B')
なのか、それとも func
自体が原因なのか?明らかに、このエラーは文脈が不足しています。
一般的な解決策
このような問題を解決するための一般的な方法は次のようなものです:
const func = () => {
try {
doSth('A');
} catch (error) {
throw new Error('An error from A', error);
}
try {
doSth('B');
} catch (error) {
throw new Error('An error from B', error);
}
};
この方法では、エラーの発生源を特定しやすくなります。ただし、いくつかの制限があります:
-
エラーの詳細が失われる
エラーが詳細な情報(例:ペイロード、HTTP ステータスコード、エラーコードなど)を含んでいる場合、この方法ではdoSth
のエラーメッセージしか新しいエラーに追加されません。他の重要な詳細(例:スタックトレース)は失われます。 -
ログの可読性の低下
エラーの発生ポイントが複数ある場合、ログが煩雑になり、解読が難しくなります。 -
意図の不明確さ
このコードでは、新しいエラーが特定のdoSth
によって引き起こされたことを明確に伝えておらず、コードの可読性に改善の余地があります。
error.cause
の導入
これらの問題に対処するため、ECMAScript 2022 では error.cause
が導入されました。
この機能を使用すると、新しいエラーオブジェクトを作成する際にエラーの原因を指定できます。error.cause
を使用することで、エラー同士を関連付けた「エラーの連鎖」を構築でき、問題の根本原因をデバッグ・追跡しやすくなります。
以下は簡単な使用例です:
try {
// エラーをスローする可能性のある操作
} catch (error) {
throw new Error('Something went wrong', { cause: error });
}
この方法により、エラー間の因果関係を構築できます。例を見てみましょう:
const func = () => {
try {
doSth('A');
} catch (error) {
throw new Error('An error from A', { cause: error });
}
try {
doSth('B');
} catch (error) {
throw new Error('An error from B', { cause: error });
}
};
このように、下層の関数(例:doSth('A')
)がスローしたエラーをキャッチし、適切な文脈(例:「doSth('A')
の実行中にエラーが発生しました」)を追加して新しいエラーをスローします。同時に元のエラーの詳細(例:「A は不正な引数です」)も保持されます。
エラー連鎖の構築
error.cause
のもう一つの利点は、複数の層にわたるエラーを連鎖的に結びつけることができる点です。これにより、問題をアプリケーション全体で追跡しやすくなります:
const func = () => {
try {
try {
try {
doSth('A');
} catch (error) {
throw new Error('Error at depth 3', { cause: error });
}
} catch (error) {
throw new Error('Error at depth 2', { cause: error });
}
} catch (error) {
throw new Error('Error at depth 1', { cause: error });
}
};
console.log(error.cause.cause); // Error at depth 3
Node.js では、cause
を持つエラーを特別に処理し、関連するエラースタックをすべて出力します:
const cause = new Error('The remote HTTP server responded with a 500 status');
const symptom = new Error('The message failed to send', { cause });
console.log(symptom);
// Prints:
// Error: The message failed to send
// at REPL2:1:17
// at Script.runInThisContext (node:vm:130:12)
// ... 7 lines matching cause stack trace ...
// at [_line] [as _line] (node:internal/readline/interface:886:18) {
// [cause]: Error: The remote HTTP server responded with a 500 status
// at REPL1:1:15
// at Script.runInThisContext (node:vm:130:12)
// at REPLServer.defaultEval (node:repl:574:29)
// at bound (node:domain:426:15)
// at REPLServer.runBound [as eval] (node:domain:437:12)
// at REPLServer.onLine (node:repl:902:10)
// at REPLServer.emit (node:events:549:35)
// at REPLServer.emit (node:domain:482:12)
// at [_onLine] [as _onLine] (node:internal/readline/interface:425:12)
// at [_line] [as _line] (node:internal/readline/interface:886:18)
結論
- エラーの文脈と詳細情報にすぐアクセスできると、デバッグが大幅に簡単になります。
- この目的を達成する効果的な方法の一つが、
error.cause
機能を活用した「キャッチ+文脈付き再スロー」パターンの採用です:
try {
doSth();
} catch (error) {
throw new Error('Context relative to doSth', { cause: error });
}
このアプローチにより、エラーの追跡性が向上するだけでなく、コードの可読性や保守性も向上します。
私たちはLeapcell、Node.jsプロジェクトのクラウドデプロイの最適解です。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:
複数言語サポート
- Node.js、Python、Go、Rustで開発できます。
無制限のプロジェクトデプロイ
- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。
比類のないコスト効率
- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。
洗練された開発者体験
- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。
Xでフォローする:@LeapcellHQ