awesome-error-standards について
APIやアプリケーションの一貫したエラー処理を設計・レビューするスキルです。開発者が障害を分類し、安全なエラー契約を定め、内部情報を漏らさず診断できるよう支援します。
主な機能
- 01運用上の障害とプログラムエラーの分類
- 02構造化された型付きエラーの設計
- 03APIエラーをHTTPステータスに対応付け
- 04境界での入力検証
- 05安全なコンテキスト付き障害ログ
- 06再試行とサイレント障害の診断
活用シーン
- 01APIエラー応答の標準化
- 02アプリケーションの障害処理のリファクタリング
- 03再試行とログ動作のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ khasky/ awesome-agent-skills --skill awesome-error-standardsインストール後、Claude Code で/awesome-error-standardsを入力して読み込まれたか確認します。
同梱ファイルevals/eval_queries.json
詳細
エラー処理のパターン
エラーのスロー、捕捉、ログ記録、通知には一貫したパターンを適用し、内部情報を漏らさず、障害を予測しやすくデバッグ可能にします。
使用するタイミング
- API やアプリのエラー処理を実装またはリファクタリングするとき
- ユーザーが「エラー処理」「エラーの対処方法」「一貫性のあるエラー」について尋ねたとき
- 適切な障害処理が行われているかコードをレビューするとき
- サービスや API のエラー契約を定義または文書化するとき
基本原則
- 運用上のエラーとプログラマーエラー — まず分類します。運用上の障害(ネットワークのタイムアウト、無効な入力、レコードの欠落)は想定内です。処理、再試行、または通知を行います。プログラマーエラー(未定義値へのアクセス、不変条件の破綻)はバグです。状態が破損したまま処理を続けるのではなく、明確にクラッシュするか、プロセスを再起動します。
- 修正するだけでなく、バグを起こせなくする — 単一箇所の修正では一つの経路しか防げませんが、多層防御ならバグの種類全体を防げます。本番環境に入る前に、次の順で予防することを優先します。(1)型システムで無効な状態を表現できないようにする(判別共用体、ブランド型/不透明型など)。これにより、不正な状態ではコンパイルできなくなります。(2)境界で検証する。(3)実行時にガードする。(4)エラーバウンダリーを設ける。重要度の高い不変条件は複数の層でガードし、外側の層を迂回しても内側の層で捕捉されることをテストします(意図的にレイヤー1をスキップし、レイヤー2で捕捉されることを確認します。モックによって検証が意図せず無効化されていないことも確認します)。 あらゆる修正を、次の段階で評価します。制御:型、API の形状、またはオプションの削除によって、間違いを起こせなくします。警告:間違いを起こした時点で知らせます(コンパイルエラー、検証の失敗、lint ルールなど)。検出:後から発見します(テスト、監視、アラートなど)。最下位の段階はコメント、ドキュメントの一文、「注意してください」です。これは修正ではなく、修正として数えません。監視は予防ではありません。すでにユーザーに届いたバグのアラートを出しても、バグを止めたのではなく、場所を移しただけです。修正の報告では、何を起こせなくしたのか、また何を意図的に起こせるままにしたのかを明記します。
- 本番環境ではなく、起動時に失敗させる — 必須の設定、環境変数、接続をすべて初期化時に検証し、値の欠落を最初のリクエストで発見するのではなく、失敗した場合は起動を拒否します。
- 速やかに失敗させる — 入力と事前条件を早期に検証し、明確なメッセージを添えてエラーをスローするか返します。無効な状態のまま処理を続けてはいけません。
- エラーを握りつぶさない — 空の catch や、エラーを無視する広範な catch は避けます。ログに記録し、必要に応じて再スローするか、結果型を返します。コンテキストを添えて伝播させます。
- 構造化されたエラー — API エラーとログには、一貫した形式(例:コード、メッセージ、詳細)を使用します。型付きのエラークラスやコードを使うと、呼び出し元が型に応じた処理を行いやすくなります。
- ユーザー向けと開発者向け — ユーザー向けのメッセージは安全で、対処方法がわかるものにします。開発者向けの詳細(スタックトレース、内部メッセージ、リクエスト ID)は、ログまたはデバッグ時にのみ提供します。スタックトレースや SQL をエンドユーザーに決して公開してはいけません。
作業手順
- 境界を特定する — エラーはどこで発生しますか(検証、DB、外部 API、認証など)。どこで処理されますか(ルートハンドラー、ミドルウェア、最上位など)。各層がエラーを処理するか、コンテキストを添えて伝播させるようにします。
- エラー形式を定義する — API の場合:HTTP ステータスと本文のエンベロープ(コード、メッセージ、任意の詳細)。コードの場合:エラークラスまたは結果型。既存のプロジェクトパターンがあれば、それに合わせます。
- エラーを HTTP に対応付ける(API の場合) — 検証 → 400 または 422、認証 → 401、認可 → 403、見つからない場合 → 404、競合 → 409、サーバーエラー → 500。エラーに対して
success: falseを含む 200 を返してはいけません。検証エラーの形式(400 か 422)を1つだけ選び、文書化したうえで、すべてのエンドポイントに適用します。動作が混在すると、生成されたクライアントや呼び出し元でエラーの分岐処理が壊れます。 - 応答前にログを記録する — コンテキスト(リクエスト ID、安全な場合はユーザー ID、操作など)を添え、適切なレベル(error/warn)でエラーを記録してから、ユーザーに安全な応答を返します。
- 検証してサニタイズする — API 境界ではスキーマ(Zod、Pydantic など)を使用し、一貫した形式でフィールド単位の検証エラーを返します。
API エラーレスポンス
標準エンベロープ:
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Must be a valid email address", "code": "invalid_format" }
]
}
}
- code — 機械可読なコードです(例:
validation_error、not_found、rate_limit_exceeded)。クライアントはこの値に応じて処理を切り替えられます。 - message — ユーザーに安全に表示できる、人間が読みやすいメッセージです。スタックトレースや内部パスは含めません。
- details — 任意項目です。バリデーションの場合は、フィールド単位のエラーを列挙します。一般的な 500 エラーでは省略します。
- request_id / trace_id — サポート用に、エンベロープまたはヘッダーに任意で含められます。内部情報は公開しないでください。
HTTP ステータス:400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、409 Conflict、422 Unprocessable Entity、429 Too Many Requests、500 Internal Server Error。意味に沿って使い、エラーに 200 を使わないでください。
失敗した呼び出しの再試行(クライアント側)
再試行が有効かどうかで、すべての失敗を分類してください。再試行できないエラーを再試行しても時間の無駄になり、処理が二重に実行される可能性があります。
- 再試行しない(リクエストを修正する):400、401、403、404、415、ほとんどの 422 — 入力または認証に誤りがあるため、再試行しても変わりません。
- リクエストを変更して再試行する:402(チャージする/プランを変更する)、一部の 422(ペイロードを調整する)。むやみに再試行しないでください。
- バックオフして再試行する:429、500、502、503、504 — 一時的なエラーです。試行回数(約 5 回)と合計待機時間(約 30 秒)に上限を設け、群衆の殺到を避けるためにジッターを加えた指数バックオフを使用します。
ルール:
- サーバーのシグナルに従ってください。
Retry-AfterおよびX-RateLimit-Reset/X-RateLimit-*ヘッダーがある場合は、独自のタイマーよりも優先してください。 - 状態を変更するリクエストを再試行する場合は、Idempotency-Key を使用してください。課金、作成、送信を行う POST/PATCH は、レスポンスが失われてもサーバー側で成功している場合があり、無条件に再試行すると二重課金などが発生します。サーバーが重複を除外できるよう、安定した冪等性キーを送信してください。
- ストリーミング(SSE/websocket)では、ストリーム内のイベント(
errorイベントまたは終了フレーム)としてエラーが届きます。ストリームが開いた時点でステータスはすでに 200 になっているためです。最初のレスポンスコードだけでなく、ストリームのエラーチャネルを明示的に処理してください。
コードパターン
型付きエラー(推奨):
class NotFoundError extends Error {
constructor(resource: string) {
super(`${resource} not found`);
this.name = 'NotFoundError';
}
}
// Caller: catch (e) { if (e instanceof NotFoundError) return res.status(404).json(...); }
レイヤー化された処理:下位レイヤーでは例外をスローするか Result を返し、ルートハンドラーまたはミドルウェアがそれを捕捉して HTTP レスポンスに変換し、ログを記録します。すべてのレイヤーで捕捉するのではなく、伝播させて境界で処理してください。
非同期処理:非同期関数では try/catch を使います。promise の拒否が処理されるようにしてください(グローバルハンドラーや .catch などを使用)。これにより、ログに記録され、未処理のままになりません。unhandledRejection/uncaughtException(またはプラットフォームに相当するもの)の境界ハンドラーを登録してください。コンテキストを含めてログに記録し、プログラマーエラーの場合は終了します。破損した状態から処理を再開しないでください。
エラーのログ記録
- レベル:対応が必要な失敗には error、復旧可能な失敗(再試行、フォールバックなど)には warn を使います。
- 内容:メッセージ、エラーの種類、スタック(サーバー側のみ)、request_id を記録します。シークレットや個人を特定できる情報(PII)全体は記録しないでください。
- 一度だけ:エラーを処理する境界でログに記録します。同じエラーをすべてのレイヤーで記録するのは避けてください。
- 安全なシリアライズ:ログ/エラー処理では、生の
JSON.stringifyではなく、安全なシリアライザー(safeStringify、構造化ロガー独自のシリアライザー)を使用してください。JSON.stringifyは循環参照やBigIntで例外をスローし、失敗を報告しようとしているハンドラー自体をクラッシュさせる可能性があります。
ルール
- 既存のプロジェクトパターン(エラークラス、レスポンス形式、ログ記録)に合わせてください。既存のパターンがない場合は、最小限で一貫性のあるパターンを導入し、文書化してください。
- 内部情報(スタックトレース、DB エラー、ファイルパス)をエンドユーザーに公開しないでください。サーバー側でのみログに記録します。
- バリデーションエラー:500 ではなく、フィールド単位の詳細を含む 400/422 を返してください。
サイレントな失敗
最も厄介な種類の失敗です。最初は問題なく動作し、200 を返すものの、何もせずに(あるいは誤った処理をして)ひそかに終わります。次の順に診断してください:未接続/未登録のコンポーネント → no-op に解決される未定義の参照 → ペイロードを破棄する型/シグナルの不一致 → 無視されたエラー(catch {}、|| true)→ 下流のメトリクスを気づかないうちに誤らせる、上流でのサンプリング/フィルタリング。キーが拒否されたからといって、機能が存在しないと結論づけないでください。機能を縮退させる設定を出荷する前に、そのコンポーネント独自のキーを調べ、ツール/メソッドを代替する場合は、元のものが見つからなかったと報告するのではなく、代替したことを明示してください。
チェックリスト
- 境界で入力を検証し、標準形式で検証エラーを返す
- エラーを正しい HTTP ステータスに割り当てる(エラーに 200 を返さない)
- ユーザー向けメッセージは安全で、次の行動が分かる内容にする。詳細はログのみに記録する
- 応答を返す前に、コンテキスト(リクエスト ID、操作)とともにエラーをログに記録する
- 空の catch を使わない。処理する、ログに記録して再スローする、または結果型を返す
アンチパターン
| アンチパターン | より良い方法 |
|---|---|
{ success: false, error: "..." } を含む 200 OK | 適切な HTTP ステータス(4xx/5xx)とエラーボディを使用する |
| 空の catch ブロック | ログに記録して再スローするか、Err(result) を返す。意図的に無視する場合はその旨を記載する |
エラーの破棄:Go の _ := f()、Python の裸の except:、確認されない Close() | エラーを処理するか、無視しても安全な理由をその場に記載する |
運用エラーに対してライブラリコード内で panic() / process.exit() を呼び出す | 型付きエラーを返すかスローし、境界で処理方法を決定する |
| エラー時にリクエスト/レスポンス全体をログに記録する | ID と概要をログに記録し、シークレットと個人を特定できる情報をマスキングする |
| クライアントにスタックトレースを公開する | スタックトレースはサーバー側でログに記録し、クライアントには汎用メッセージを返す |
| エンドポイントごとに異なるエラー形式を使う | API 全体で単一のエンベロープ(code、message、details)を使用する |
| トランスポートに依存するドメインエラー:サービスやリポジトリがフレームワークのレスポンスオブジェクトや HTTP ステータスコードを返す、またはスローする | トランスポートに依存しない型付きエラーをスローし、単一のグローバルハンドラーでそれらを HTTP にマッピングする。ビジネスロジックを配信方法から切り離すことで、再利用とテストが容易になる |