第17章:エラー設計①:エラーを“仕様”として分類する🚧🧩
今日のゴール🎯
- 「失敗」をタイプ別に分けて、チームで同じ言葉で話せるようにする🗣️✨
- モジュールの公開APIに、起こり得るエラーを“仕様”として載せられるようにする📣🪟
- API(HTTP)に返す形をProblem Detailsで統一する準備をする📦🧠(RFC 9457) (RFCエディタ)
まずダメ例😇(あるある)
- 何でも
throw new Exception("失敗")で終わり💥 - 画面には「エラーが発生しました」だけ😭(ユーザーは何していいかわからない)
- ログにはスタックトレースだけ📄(運用は地獄)
- どの失敗が「入力ミス」なのか「業務ルール違反」なのか「障害」なのか誰も判断できない🤯
👉 結果:仕様が読めない、テストが弱い、クライアントも困る、運用も困る…😵💫
いい例😎(この章の結論)

失敗はぜんぶ「エラー」だけど、性格が違うんだよね🧠✨ だからまずは「分類」を決めちゃう!
まずは“3つ+α”でOK🧩
基本の3つ
- 入力エラー(Validation):ユーザーの入力が変なら失敗📝
- 業務ルール違反(Domain):入力は正しいけど「それはできない」🙅♀️
- インフラ障害(Infrastructure):DB/通信/外部APIなど都合で失敗🌩️
よく出る+α
- NotFound(対象がない)🔎
- Conflict(同時更新や状態競合)⚔️
- Auth(認証・権限)🔐
- Unexpected(バグっぽい)🐛
分類ごとの「扱い方」ルール📌(超大事!)
分類が決まると、次が自動で決められるよ😊✨
1) 直す人は誰?👤
- Validation:ユーザー/クライアントが直す
- Domain:仕様として“できない”(画面の案内が必要)
- Infrastructure:運用/再試行/復旧
- Unexpected:開発が直す(バグ)
2) リトライしていい?🔁
- Infrastructure:リトライの可能性あり(タイムアウト等)
- Validation/Domain:基本リトライしても無駄🙅♀️
3) 画面に見せていい?👀
- Validation:見せてOK(どこがダメか)
- Domain:見せてOK(何がルール違反か)
- Infrastructure/Unexpected:詳細は見せない(情報漏えい防止)🔒
HTTP(Web API)に返す形は「Problem Details」で揃える📦✨
今のASP.NET Coreでは、APIのエラー応答を ProblemDetails で統一するのが王道だよ🙂 標準はRFC 9457(Problem Details for HTTP APIs)で、RFC 7807を置き換える形になってるよ📜 (RFCエディタ) ASP.NET Coreのエラー処理ガイドもこの方向で整理されてるよ🧭 (Microsoft Learn)
さらに、400の既定応答が ValidationProblemDetails になってる(=バリデーションは“構造化して返す”のが普通)っていうのもポイント🧁 (Microsoft Learn)
手を動かす(C#)⌨️🧩:分類できる「エラー型」を作ろう
ここでは “分類の器” だけ作るよ! (例外とResultの使い分けは次章でしっかりやるよ😆)
namespace Modules.Shared.Errors;
public enum ErrorKind
{
Validation, // 入力が変
Domain, // 業務ルール違反(仕様)
NotFound, // 対象がない
Conflict, // 状態競合(同時更新など)
Unauthorized, // 未ログイン
Forbidden, // 権限なし
Infrastructure, // DB/外部I/O
Unexpected // 想定外(バグの疑い)
}
public sealed record AppError(
string Code, // 機械向け(例: ORDER_CART_EMPTY)
string Title, // 短い説明
string? Detail, // 人間向け(必要最小限)
ErrorKind Kind,
bool IsRetryable = false
);
public static class Errors
{
public static AppError Validation(string code, string title, string? detail = null)
=> new(code, title, detail, ErrorKind.Validation);
public static AppError Domain(string code, string title, string? detail = null)
=> new(code, title, detail, ErrorKind.Domain);
public static AppError NotFound(string code, string title, string? detail = null)
=> new(code, title, detail, ErrorKind.NotFound);
public static AppError Conflict(string code, string title, string? detail = null)
=> new(code, title, detail, ErrorKind.Conflict);
public static AppError Infrastructure(string code, string title)
=> new(code, title, null, ErrorKind.Infrastructure, IsRetryable: true);
public static AppError Unexpected(string code = "UNEXPECTED", string title = "Unexpected error")
=> new(code, title, null, ErrorKind.Unexpected);
}
エラーコード命名のミニルール📛
- 大文字スネーク:
ORDER_ALREADY_PAID/CATALOG_PRODUCT_NOT_FOUND - モジュール名を先頭にしてもOK:
ORDERING_ORDER_ALREADY_PAID(大規模になったら効く)💪
例:Orderingの「注文確定」で起こり得る失敗を分類してみる🛒✨
「注文する」は、だいたいこう分かれるよ👇
- Validation:住所が空、数量が0、形式が変📝
- Domain:カート空、在庫不足、支払い済み🙅♀️
- NotFound:カートが存在しない🔎
- Conflict:同時に二重確定しようとしてる⚔️
- Infrastructure:DBタイムアウト、外部決済が落ちた🌩️
境界(公開API)で「HTTPステータス」にマップする🧭
分類があると、HTTPも迷わない😊 (ここは“方針”を固定しちゃうのが勝ち!)
- Validation → 400(ValidationProblemDetailsが相性◎) (Microsoft Learn)
- Domain → 409(または422)※「仕様としてできない」
- NotFound → 404
- Conflict → 409
- Unauthorized → 401
- Forbidden → 403
- Infrastructure → 503(or 500)+ リトライ可の合図🔁
- Unexpected → 500(詳細は隠す)🔒
そして返す本文は ProblemDetails に寄せるのが今どきの流れだよ📦 (Microsoft Learn)
ミニ演習📝✨
次のユースケースで「起こり得る失敗」を最低3つずつ出して、分類してみてね😊 (Catalog / Ordering / Identity を意識してね🧩)
- 商品検索(Catalog)🔎
- 注文確定(Ordering)🛒
- ログイン(Identity)🔐
💡できたら、各エラーに
- Code(例:
ORDER_CART_EMPTY) - Kind(Validation/Domain…)
- ユーザーに見せるTitle(短く) を付けて“仕様リスト”にしてみよう📋✨
AI活用プロンプト例🤖✨
- 「このAPI(注文確定)で起こり得る失敗を、Validation/Domain/Infrastructure/NotFound/Conflict/Authに分類して。エラーコード案も付けて」
- 「業務ルール違反(Domain)のエラーだけ、ユーザー向けメッセージを“短く・否定しない言い方”で10案出して」
- 「ProblemDetailsで返すときのstatus/title/detail/extensions(code)の設計案を出して」
まとめ(覚える1行)📌
**エラーは“例外処理の話”の前に、まず“仕様として分類”する!**🚧✨
次章(第18章)で、この分類を使いながら「例外とResultをどう使い分ける?」を気持ちよく整理していくよ〜😆✅
(ちなみに今は .NET 10 / C# 14 が最新で、Visual Studio 2026 から使えるよ🪟✨) (Microsoft Learn)