【Haskell学習|実務向け】実務で差がつく!例外にコンテキストを付加する「Annotated Exceptions」のすすめ

導入:なぜ「エラーの発生場所」だけでは足りないのか

実務におけるシステム運用で最も頭を悩ませるのは、「何が起きたか」はわかるのに「なぜ起きたか」が特定できないエラーです。ログに「File not found」とだけ出力されても、それが「どのユーザーの」「どのリクエストから発生したものか」が分からなければ、再現手順の特定に膨大な時間を要します。Annotated Exceptions(注釈付き例外)は、例外に文脈(コンテキスト)を付加して再スローすることで、デバッグの質を劇的に向上させる手法です。

基礎知識:例外のラップ(Wrapping)とは

例外のラップとは、発生した低レベルの例外を、より上位のレイヤーで「捕まえて」、詳細な情報を付加した新しい例外として「投げ直す」技術です。関数型プログラミングの文脈では、副作用を管理しつつエラーを伝播させる際に重要となります。単なるエラーメッセージの連結ではなく、構造化されたデータとしてコンテキストを持たせることが、後のログ解析を容易にする鍵となります。

実装:コンテキストを付加する論理構造

実装のアプローチはシンプルです。元の例外(Cause)を保持したまま、新しい例外インスタンスを作成します。この際、現在の関数の引数や実行コンテキストをデータとして保持させます。これにより、スタックトレースを追うだけでなく、エラー発生時の具体的なパラメータを確認することが可能になります。

サンプルプログラム:TypeScriptによる実装例

以下は、Node.js等の環境で利用できる、コンテキストを付加する例外クラスの実装例です。

// 詳細なコンテキストを保持するためのカスタム例外クラス
class AnnotatedError extends Error {
  constructor(public message: string, public context: Record<string, any>, public cause?: Error) {
    super(message);
    this.name = 'AnnotatedError';
  }
}

// ユーザー情報に基づいてファイルを処理する関数
async function processUserFile(userId: string, filename: string) {
  try {
    // ここでエラーが発生すると仮定
    throw new Error('File not found');
  } catch (err) {
    // 発生元のエラーをラップし、コンテキスト(userId等)を付加して再スロー
    throw new AnnotatedError(
      'ユーザーファイルの処理中にエラーが発生しました',
      { userId, filename },
      err as Error
    );
  }
}

// 実行とログ出力
async function main() {
  try {
    await processUserFile('user_123', 'data.json');
  } catch (err) {
    if (err instanceof AnnotatedError) {
      // 構造化されたログとして出力することで、監視ツールでの検索性が向上
      console.error('エラー詳細:', {
        message: err.message,
        context: err.context,
        originalError: err.cause?.message
      });
    }
  }
}

main();

応用・注意点:現場で陥りやすい罠

過剰なラップに注意: 全ての関数で例外をラップすると、スタックトレースが冗長になりすぎ、逆に可読性を損ないます。ビジネスロジックの境界や、外部APIとの通信など「エラーの理由を特定したい重要な箇所」に絞って適用するのがコツです。

機密情報の混入: コンテキストにユーザーのパスワードや個人情報をそのまま含めないよう注意してください。ログ管理基盤に転送する前に、フィルタリングを行うか、必要な識別子(IDなど)のみを付加する運用ルールを徹底しましょう。この手法を取り入れるだけで、障害対応時の「手探り状態」から脱却できるはずです。

コメント

タイトルとURLをコピーしました