Skip to main content

第16章:エラーも契約①:エラーの分類と境界😵‍💫➡️🙂

Error Bins

この章でできるようになること🎯✨

  • 「この失敗、どの種類のエラー?」を3分類で言えるようになる🗂️
  • “境界(boundary)”でエラーを整えて、利用者に優しい形に変換できるようになる🎁
  • catch の中で安全に扱えるように、型の守り方がわかる🛡️

1) まず大事な考え方:エラーも“契約”の一部だよ📜⚠️

関数やAPIって「入力したら、こう返すよ」って約束(契約)をしてるよね🤝 で、成功だけじゃなくて失敗も約束の一部なんだよ〜!

たとえば👇

  • ✅ 成功:User が返る
  • ❌ 失敗:ユーザーが存在しない入力が変DBに繋がらない…など

ここがふわっとしてると、使う側は毎回こうなる😇

  • 「どんな失敗があり得るの?」
  • 「どれは直せる?どれは待つしかない?」
  • 「ログはどう出すの?」

だからこの章は、**“失敗の種類を整理して、境界で整える”**の練習をするよ💪✨


2) エラー3分類🧁🧠(この教材ではこの3つに固定!)

この章ではエラーを次の3つに分けるよ📛

A. ドメインエラー(Domain Error)🏷️💡

意味:ビジネス的に「それはダメだよ」っていう失敗

  • すでに退会済みのユーザーが「退会する」ボタン押した
  • 残高が足りないのに購入しようとした
  • 同じメールアドレスで新規登録しようとした

✅ 特徴:入力が正しくても起きる(ルール違反だから) 👉 利用者に“直し方”を案内できることが多いよ🫶


B. 入力エラー(Validation / Input Error)📝🚫

意味:入力の形・型・必須項目などがダメ

  • email が空
  • age が文字列で来た
  • page-1
  • JSONの必須フィールドが欠けてる

✅ 特徴:ほぼ利用者が直せる 👉 だから エラー文は“直しやすさ”が超大事(第17章で本格的にやるよ🍰)


C. インフラエラー(Infrastructure Error)🌩️🔌

意味:ネットワーク・DB・外部API・ファイルなど、外部要因の失敗

  • DB接続失敗
  • タイムアウト
  • 外部APIが落ちてる
  • 権限不足でファイル読めない

✅ 特徴:利用者が直せないことも多い 👉 だから “再試行してね” とか “後でもう一回” が現実的な案内になりがち🙂


3) 境界(boundary)ってなに?🧱🚪

境界はカンタンに言うと、こういう“つなぎ目”のこと!

  • モジュールとモジュールの境目
  • ドメイン層 ↔ アプリ層の境目
  • サーバ内部 ↔ HTTPレスポンスの境目
  • 自分のコード ↔ 外部ライブラリの境目

ここでやることは1つだけ👇 ✅ 内部で起きたごちゃっとした失敗を、外に出す前に“整形(翻訳)”する🎀


4) なんで境界で整えるの?(やらないと起きる悲劇😱)

もし境界で整えないと…

  • DB_CONNECTION_FAILED がそのまま画面に出てユーザー困惑😵
  • どの失敗も Error: something went wrong で、呼び出し側が詰む🧊
  • ログに必要情報が残らず、デバッグ地獄🌀

なのでこの章の合言葉は👇 **「内部は自由、外向きは整える」**🪄✨


5) TypeScriptで“分類”をコードに落とす🟦🧩

5-1) catch の中は“unknown”が基本だよ🕵️‍♀️

catch (e)eunknownとして扱う設定があるよ。これを使うと、雑に e.message とか触れなくなるので安全になる🛡️ (useUnknownInCatchVariables)(typescriptlang.org)

狙い

  • 「throwされるものはErrorとは限らない」現実にちゃんと対応する
  • 例外処理が“ちゃんと確認してから扱う”スタイルになる✨

5-2) 3分類を“型”で表現する(おすすめは判別しやすい形)🧷✨

「種類」が一瞬でわかるように、**識別子(kind)**を入れておくのが超おすすめ🎀

type DomainError = {
kind: "domain";
code: "USER_ALREADY_DELETED" | "INSUFFICIENT_BALANCE";
message: string; // 利用者向けの短い説明(第17章で磨く✨)
};

type InputError = {
kind: "input";
field?: string; // どこがダメ?が分かると親切🧡
message: string;
};

type InfraError = {
kind: "infra";
service: "db" | "externalApi" | "file";
message: string; // 外向けは控えめに(詳細はログへ)
};

type AppError = DomainError | InputError | InfraError;

ここまで作ると、境界で👇みたいに分岐できるようになるよ🥳


6) 境界でやる“翻訳”の基本パターン🧠➡️🧾

パターン①:外部ライブラリの例外をキャッチして、インフラエラーにする🌩️

内部ではライブラリがいろんな形でthrowしてくるから、境界で統一する!

そして大事なのが 原因(cause)をつなげること💡 new Error(message, { cause }) で「元の失敗」を保持できるよ。(MDN Web Docs)

function toInfraError(e: unknown): InfraError {
// まずは「何が投げられたか分からない」前提で安全に扱う
if (e instanceof Error) {
return {
kind: "infra",
service: "db",
message: "データ取得に失敗しました",
};
}

// Errorですらないものが投げられることもある😇
return {
kind: "infra",
service: "db",
message: "予期しない失敗が発生しました",
};
}

async function loadUser(userId: string) {
try {
// ここは例:DBアクセス
// await db.user.find(...)
} catch (e) {
// 内部の原因は cause としてつなぐ(ログや調査が楽になる✨)
throw new Error("loadUser failed", { cause: e });
}
}

ポイント🎀

  • 利用者向けメッセージと、内部原因(cause)を分ける
  • causeで“根っこ”を残すと、調査がめちゃくちゃ楽になる🕵️‍♀️✨ (MDN Web Docs)

パターン②:アプリの外(HTTPなど)へ出す直前で、3分類→“外向けの形”に整える🌐🎁

HTTP APIの場合、エラー応答の形式を標準化する方法として Problem Details(RFC 9457)があるよ。(datatracker.ietf.org) (本格的なAPI契約は後の章でやるけど、境界の考え方としてチラ見せ👀✨)

例イメージ👇

  • input → 400
  • domain → 409(競合)や 422 など(方針次第)
  • infra → 503(サービス不可)や 500

7) “どこまで外に見せる?”の線引きルール📏✨

ここ、初心者が一番迷うところ!なのでシンプルに覚えよう🧠💕

  • 入力エラー:なるべく詳しく(どこがダメか)📝
  • ドメインエラー:ルール違反が分かるように(でも内部事情は出しすぎない)🏷️
  • インフラエラー:外には控えめ(詳細はログ)🌩️

つまり👇 外向け:短く・直し方が分かる 内向け:原因が追える(ログ・cause・ID) この二刀流が強いよ⚔️✨


8) ミニ演習:エラー一覧を3分類してみよう📛🗂️(15〜25分)

あなたのアプリ(または架空のアプリ)で、次を作ってみてね🫶

ステップ1:エラー候補を10個書く📝

例:ログイン/商品購入/ファイルアップロード など、好きな題材でOK🎀

ステップ2:3分類に仕分ける🗂️

  • ドメイン:___
  • 入力:___
  • インフラ:___

ステップ3:境界を1個決める🚪

例:

  • controller(HTTPの入口)
  • service(アプリ層の入口)
  • repository(DBの入口)

そこで「外に出す形」をどう整えるか、軽くメモする✍️✨ (第17章で“優しい返し方”をガッツリ磨くよ🍰)


9) AI活用プロンプト例🤖💬(コピペOK✨)

  • 「この機能で起こり得るエラーを ドメイン/入力/インフラ に分類して、表にして」🗂️🤖
  • 「この例外処理、境界で翻訳できてる?改善案を3つ出して」🚪🤖
  • catch で unknown を安全に扱うガード関数を提案して」🛡️🤖
  • 「このエラー一覧、利用者に見せていい情報/ダメな情報を分けて」🔒🤖

10) まとめ✅✨(この章の持ち帰り)

  • エラーは“失敗時の契約”だから、ふわっとさせない📜⚠️
  • まずは ドメイン/入力/インフラ の3分類で整理する🗂️
  • 境界で翻訳して「外向けは分かりやすく、内側は調査しやすく」🎁🔍
  • catch は unknown前提で安全に扱うのが強い🛡️(typescriptlang.org)
  • 原因を残すなら Errorcause が便利💡(MDN Web Docs)