Skip to content
cccskills
FreePublished on GitHub

error-handling-standards

Enforces error handling standards: silent error prohibition, custom error class hierarchy (AppError base with category never-fallback / transient / permanent, cause preservation, ValidationError / UnauthorizedError / UpstreamError etc. — canonical definitions in ERRORS.md), external-boundary normalization (normalizeExternalError), Result pattern (Result.ok/Result.fail), proper try-catch with error type checking, structured error logging with a single Logger contract, fallback prohibition categories (isNeverFallback), and HTTP status code mapping. Use when implementing error handling, reviewing catch blocks, or designing error responses.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md15.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

エラーハンドリング基準

サイレントエラーゼロトレランスを原則とし、すべてのエラーを適切に分類・処理・記録するためのスキル。 ERRORS.md、PATTERNS.md のセクション 9 および FALLBACK.md で定義されたパターンを適用する。

1. サイレントエラーの禁止

以下のパターンはすべて禁止:

// ❌ 空の catch ブロック
try {
  doSomething();
} catch (e) {}

// ❌ console.log のみでエラーを握りつぶし
try {
  doSomething();
} catch (e) {
  console.log(e);
}

// ❌ エラーを無視して null/undefined を返す
try {
  return fetchData();
} catch (e) {
  return null;
}

// ❌ 汎用的すぎるエラーメッセージ
throw new Error("Something went wrong");

すべての catch ブロックは、エラーの記録、再スロー、またはResult.fail での返却のいずれかを行うこと。

例外: 環境分岐付きフォールバック(セクション8参照)は本番環境のみで許容される。無条件のフォールバックは禁止。

2. カスタムエラークラス階層

プロジェクトでは AppError を基底クラスとしたエラー階層を使用する。定義の正典は ERRORS.md(本ファイルには基底クラスと 1 例だけを載せ、サブクラスの全一覧は複製しない — 複製は必ずドリフトする):

// options.cause で元エラーを保持する(ES2022 Error.cause。tsconfig の lib に ES2022 が必要)
type AppErrorOptions = { cause?: unknown };

// 分類。フォールバック可否・再試行可否は statusCode から推測せず、各サブクラスが宣言する
type ErrorCategory = "never-fallback" | "transient" | "permanent";

abstract class AppError extends Error {
  abstract readonly category: ErrorCategory;
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number,
    options?: AppErrorOptions,
  ) {
    super(message, options);
    this.name = this.constructor.name;
  }
}

// バリデーション詳細(any を使わない / MASTER.md)。ERRORS.md と同じ形状
interface ValidationDetail {
  field: string;
  message: string;
  constraint?: string;
}

// サブクラスの例(他の NotFoundError / ForbiddenError / ConflictError / UnauthorizedError /
// SecurityError / InternalError / UpstreamError / UpstreamRejectedError / UnknownExternalError は ERRORS.md)
// HTTP_STATUS は ERRORS.md で定義(`./errors` から import)
class ValidationError extends AppError {
  readonly category: ErrorCategory = "never-fallback";
  constructor(
    message: string,
    public readonly details: readonly ValidationDetail[],
    options?: AppErrorOptions,
  ) {
    super(message, "VALIDATION_ERROR", HTTP_STATUS.BAD_REQUEST, options);
  }
}

3. エラーコードと HTTP ステータスコード

エラークラスエラーコードHTTP ステータス分類用途
ValidationErrorVALIDATION_ERROR400never-fallback入力バリデーション失敗
UnauthorizedErrorUNAUTHORIZED401never-fallback未認証
ForbiddenErrorFORBIDDEN403never-fallback権限不足
SecurityErrorSECURITY_VIOLATION403never-fallback署名不一致・改ざん検知
NotFoundErrorNOT_FOUND404permanentリソース未検出
ConflictErrorCONFLICT409never-fallback重複・競合
InternalErrorINTERNAL_ERROR500permanent予期しない内部エラー
UpstreamErrorUPSTREAM_UNAVAILABLE502transient外部サービスの一時障害(再試行可)
UpstreamRejectedErrorUPSTREAM_REJECTED502never-fallback外部サービスによる恒久的な拒否(自コードの要求誤り。本番でフォールバックしない)
UnknownExternalErrorUNKNOWN_EXTERNAL_ERROR502never-fallback原因不明の外部エラー(推測で再試行・フォールバックしない)

すべて ERRORS.md で定義済み。新しいエラー種別が必要な場合は、必ず AppError を継承し category を宣言して作成する(宣言しないとコンパイルエラーになる)。

4. Result パターン

ビジネスロジックでは例外スローではなく Result パターンを優先する:

// Result 型
type Result<T> = { ok: true; value: T } | { ok: false; error: AppError };

const Result = {
  ok: <T>(value: T): Result<T> => ({ ok: true, value }),
  fail: <T>(error: AppError): Result<T> => ({ ok: false, error }),
};

// 使用例
async function processUser(userId: string): Promise<Result<User>> {
  try {
    const user = await userRepository.findById(userId);
    if (!user) {
      return Result.fail(new NotFoundError("User not found"));
    }

    const processed = await processUserData(user);
    return Result.ok(processed);
  } catch (error) {
    logger.error("Failed to process user", error, { userId });

    if (error instanceof AppError) {
      return Result.fail(error);
    }

    return Result.fail(
      new InternalError("Processing failed", { cause: error }),
    );
  }
}

Result パターンのメリット:

  • 呼び出し側がエラーハンドリングを忘れない(型で強制)
  • 正常系と異常系が型レベルで明確に区別される
  • try-catch のネストが減りコードが読みやすくなる

5. Try-Catch のベストプラクティス

// ✅ 良い例: 外部 SDK / HTTP の境界で正規化し、エラー型に応じて処理
try {
  await riskyOperation();
} catch (error) {
  const normalized = normalizeExternalError(error);
  if (normalized instanceof ValidationError) {
    return Result.fail(normalized); // そのまま返却
  }
  if (normalized instanceof NotFoundError) {
    // warn の meta に Error 実体は入れない(Logger 規約 / PATTERNS.md §9)
    logger.warn("Resource not found", { code: normalized.code });
    return Result.fail(normalized);
  }
  logger.error("External operation failed", error);
  return Result.fail(normalized);
}

// ❌ 悪い例: 汎用的な catch のみ
try {
  await riskyOperation();
} catch (error) {
  throw new Error("Failed"); // 元のエラー情報が失われる
}

// ✅ 外部境界で再スローするなら分類と元の cause を保持する
try {
  await riskyOperation();
} catch (error) {
  throw normalizeExternalError(error);
}

ルール:

  • instanceof でエラー型をチェック
  • 具体的なエラーから順に処理
  • 内部の不具合と確認したエラーは InternalError でラップして再スロー({ cause } で元の値を保持)。分類不能な外部エラーは normalizeExternalError() に渡し UnknownExternalError(never-fallback)にする
  • 元のエラー情報は必ずログに記録
  • 外部 SDK / HTTP クライアントのエラーは境界で normalizeExternalError()(ERRORS.md)により AppError へ正規化する

6. 構造化エラーログ

エラーログは JSON 形式で構造化し、必要なコンテキストを含めること。呼び出し規約はテンプレート全体で 1 つで、**正典(interface Logger)と実装例(JsonLogger)は PATTERNS.md §9「ログパターン」**にある。ここには複製せず、規約だけを示す:

  • error(message, error, meta?) — 第 2 引数は unknown。元の値をそのまま渡し、PATTERNS.md §9 の serializeError で構造化する
  • warn(message, meta?) / info(message, meta?) — meta は構造化コンテキスト。Error 実体は入れない(name / code だけ載せる)
// 使用例
try {
  await riskyOperation(userId);
} catch (error) {
  logger.error("Failed to process user", error, {
    userId,
    operation: "riskyOperation",
    requestId: req.headers["x-request-id"],
  });
}

ログの必須フィールド:

  • level: エラーレベル(error / warn / info)
  • message: 人間が読めるエラー説明
  • error: serializeError() が返す構造化情報。Error は name / message / stack、非 Error は status / body または shape など、実際に取得できたフィールドを記録する(欠けた情報を作らない)
  • timestamp: ISO 8601 形式

禁止事項:

  • 個人情報(パスワード、メールアドレス等)をログに含めない
  • スタックトレースをユーザー向けレスポンスに含めない

7. チェックリスト

エラーハンドリングのコードレビュー時に確認する項目:

  • 空の catch ブロックがないか
  • すべてのエラーが適切にログ記録されているか
  • カスタムエラークラス(AppError 継承)を使用しているか
  • エラーコードと HTTP ステータスが正しくマッピングされているか
  • Result パターンが適切に使用されているか
  • instanceof でエラー型をチェックしているか
  • 内部不具合は InternalError、分類不能な外部エラーは UnknownExternalError として区別し、元の cause を保持しているか
  • ログに個人情報が含まれていないか
  • ユーザー向けレスポンスにスタックトレースが露出していないか
  • フォールバック処理は明示的な production だけで許可し、禁止カテゴリは環境にかかわらずスローしているか
  • AI生成のtry-catch + デフォルト値返却パターンが残っていないか

8. AI生成コードのフォールバックアンチパターン

包括的なフォールバック戦略(階層モデル・レイヤー別パターン含む)は FALLBACK.md を参照。

AI(Claude Code, Copilot, Cursor等)は try-catch + デフォルト値返却を自動挿入する傾向がある。 このパターンは開発中のバグを隠蔽し、本番で初めて問題が発覚するリスクを生む。

検出すべきアンチパターン

// ❌ パターン1: 空 catch + デフォルト値
try {
  return await fetchData();
} catch {
  return defaultValue;
}

// ❌ パターン2: エラー無視 + 空配列/空文字
try {
  return await getItems();
} catch {
  return [];
}

// ❌ パターン3: catch 内で console.log のみ + フォールバック
try {
  return await getData();
} catch (e) {
  console.log(e);
  return fallbackData;
}

// ❌ パターン4: Promise.catch() でサイレントフォールバック
const data = await fetchData().catch(() => defaultValue);

修正パターン

フォールバックが必要な場合は、fallbackInProdOnly() ユーティリティ(推奨)または環境分岐を使用する:

// ✅ 推奨: ユーティリティを使う(禁止カテゴリの判定を内蔵している)。
// 引数は AppError に限定されるので、HTTP 境界の生エラーは normalizeExternalError() を通す
try {
  return await fetchData();
} catch (error) {
  return fallbackInProdOnly(defaultValue, normalizeExternalError(error), {
    operation: "fetchData",
  });
}

// △ インライン環境分岐(カスタムログが必要な場合のみ)
// 素の環境分岐は「フォールバック禁止カテゴリ」の判定を持たないため、
// 認証・認可・バリデーション・データ整合性・セキュリティ・上流の恒久的な拒否のエラーが
// 本番で握りつぶされる。この形を使うなら禁止カテゴリの判定を必ず添える。
try {
  return await fetchData();
} catch (error) {
  const normalizedError = normalizeExternalError(error);
  logger.error("Failed to fetch data", normalizedError, {
    operation: "fetchData",
  });

  // 禁止カテゴリは環境に関係なく常にスロー(FALLBACK.md Section 1 / §4 の isNeverFallback)。
  // 判定を自前で再実装しない — 禁止カテゴリの定義が変わったときに取り残される
  if (isNeverFallback(normalizedError)) {
    throw normalizedError;
  }

  const env = process.env.NODE_ENV;
  if (env !== "production") {
    throw normalizedError; // 未設定・未知の値・開発・テストは fail-fast
  }

  return defaultValue; // 本番時のみ: UX保護
}

レビュー時の判断基準

状況対応
catch 内でデフォルト値を返している環境分岐を追加するよう指摘
.catch(() => default) パターンtry-catch + 環境分岐に書き換え
認証/認可/バリデーション/データ整合性/セキュリティ/上流の恒久的な拒否のエラーにフォールバック環境問わずスローに修正
既に fallbackInProdOnly() を使用OK(ログ記録・エラー正規化を確認)
環境分岐のみ(禁止カテゴリの判定なし)禁止カテゴリの判定を追加するよう指摘
フォールバックが明示的にビジネス要件コメントで理由を明記させる

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

プロジェクトガイドラインへの準拠をチェックし、バグ、スタイル違反、コード品質問題を検出する。PRレビュー時に使用。

The English summary is being prepared. Showing the original description.

feel-flow/ai-spec-driven-development7Updated Oct 18, 2026

Enforces project coding standards during code review: naming conventions (PascalCase for classes, camelCase for methods/variables, UPPER_SNAKE_CASE for constants, kebab-case for files), standard file structure, anti-magic-number policy, file size limits (500/800 lines), security patterns, and design patterns. Use when reviewing code, writing new code, or checking adherence to project conventions.

The English summary is being prepared. Showing the original description.

feel-flow/ai-spec-driven-development7Updated Oct 18, 2026

機能を保持したまま不要な複雑性を排除し、コードの簡潔性と可読性を向上させる。30行超の関数や3段以上のネスト検出時に使用。

The English summary is being prepared. Showing the original description.

feel-flow/ai-spec-driven-development7Updated Oct 18, 2026

コードコメントの正確性、完全性、長期的な保守性を分析する。JSDoc、コメント、README等のドキュメントが変更された場合に使用。

The English summary is being prepared. Showing the original description.

feel-flow/ai-spec-driven-development7Updated Oct 18, 2026

サイレント失敗を検出し、堅牢なエラーハンドリングを確保する。PRレビューでのエラーハンドリング検査時に使用。

The English summary is being prepared. Showing the original description.

feel-flow/ai-spec-driven-development7Updated Oct 18, 2026

Creates and reviews agent skills safely by enforcing deletion-prevention harness rules: no fixed absolute temp paths, no cwd-dependent cleanup, no wildcard deletes, no workspace-external file operations, no silent cleanup failures, and mandatory temp-root validation before any destructive command. Use when creating a new skill, updating an existing SKILL.md, or reviewing prompts/runbooks that include temp files, extraction, cleanup, PowerShell, bash, rm, del, Remove-Item, or archive expansion.

The English summary is being prepared. Showing the original description.

feel-flow/ai-spec-driven-development7Updated Oct 18, 2026

See all skills by feel-flow

Report a problem with this skill