error-handling について
Epicenter プロジェクトで wellcrafted を使い、例外と `Result` 値を扱うためのコーディングスキルです。エラー契約を明確にし、型付きの失敗を保ち、すべてのエラー分岐を処理する方法を示します。
主な機能
- 01例外と `Result` の契約選択
- 02同期・非同期処理のラップ
- 03既知の失敗を変換し未知の例外を再送出
- 04すべての `Err` 分岐の処理
- 05非同期結果の所有者を明確化
活用シーン
- 01例外を送出する API を `Result` 戻り値に変換
- 02未知の例外をプログラムエラーとして維持
- 03`Result` の消費経路の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ epicenterhq/ epicenter --skill error-handlingインストール後、Claude Code で/error-handlingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/http-boundaries.mdtoast-on-error.mdwrapping-boundaries.md詳細
エラー処理
このスキルでは、スローされる例外と Result 値の境界、および Result を適切に処理する方法を扱います。バリアントの設計には define-errors、診断には logging、RPC の提示には query-layer、レスポンス API には hono を組み合わせてください。
信頼できる情報源
Wellcrafted の動作に関する主張はすべて、公式の wellcrafted-dev/wellcrafted のソースコードとテストに基づいてください。このガイダンスを保守する際は、Epicenter にインストールされているバージョンが参照しているソースのバージョンと一致することを確認してください。一致しない場合は、バージョンのずれを報告してください。依存関係の最新化は、このスキルの範囲外です。他のスキル、例、生成されたドキュメント、DeepWiki は手がかりとして扱い、信頼できる情報源とは見なさないでください。
必要に応じて、対象を絞ったリファレンスを参照してください。
- 1 つの
trySyncまたはtryAsyncでどの範囲の処理を包むか、特にクリーンアップに関して判断する場合は、references/wrapping-boundaries.md を参照してください。 - UI コードでタグ付きの失敗を提示する場合は、references/toast-on-error.md を参照してください。
- 失敗を Hono のレスポンスにマッピングする場合や、伝播させるべき例外を判断する場合は、references/http-boundaries.md を参照してください。
まず契約を選ぶ
| 必要な契約 | パターン |
|---|---|
呼び出し元が Result<T, E> を受け取る | スローする処理を trySync または tryAsync で適合させる |
| 失敗時に有効なフォールバックがある | catch から Ok(fallback) を返す |
| 失敗をデータとして伝播させる必要がある | catch から型付きの defineErrors ファクトリーの結果を返す |
既知の外部エラーだけを Err にする | 既知の例外をマッピングし、未知の例外は再スローする |
| 周囲の API が例外ベースである | そのスローする契約を維持する |
| 成功、失敗、キャンセル、早期リターンのいずれでもクリーンアップを実行する必要がある | finally を使う |
同期処理には trySync を、Promise を返す処理には tryAsync を使ってください。
const { data: response, error } = await tryAsync({
try: () => fetch(url),
catch: (cause) => RequestError.TransportFailed({ cause }),
});
if (error !== null) return Err(error);
return Ok(response);
defineErrors ファクトリーは、すでに Err(...) を返します。生の cause をファクトリーに渡し、extractErrorMessage を使ったメッセージの組み立てはファクトリーに任せてください。catch 境界で生の Err(cause) を使わないでください。スローされる値は null または undefined の場合があり、型付けされていない cause ではドメインの失敗を表せません。
意図的なスロー境界を越える
呼び出し元が回復、報告、再試行、または伝播できる間は、Result をデータとして保持してください。周囲の API が失敗を例外で伝える場合に限り、unwrap を使ってください。
import { unwrap } from 'wellcrafted/result';
const document = unwrap(await openDocument(id));
unwrap は Ok.data を返し、Err.error をスローします。このスローは境界の契約であり、失敗が予期せぬものだったことを示すものではありません。この関数で回復、コンテキストの追加、またはクリーンアップを行う必要がある場合は、代わりに error !== null を条件にしてください。
起こりうるすべての Err 分岐を処理する
- 値が
Result<T, E>になりうる場合は、エラー分岐を確認するか、意図的に転送してください。 - 分割代入の後、
errorは生のEです。errorではなくErr(error)を返してください。 - Result 全体を保持している場合は、そのまま返してください。
if (result.error !== null) return result。 error !== nullが確実な判別方法です。Err(null)やErr(undefined)を構築しないでください。- catch 分岐が常に
Ok<T>を返し、推論される型がOk<T>に絞り込まれる場合は、データのみの分割代入が適切です。 - 成功データが不要な場合は、エラーのみの分割代入が適切です。使わないフィールドまで分割代入するのではなく、起こりうるすべての
Errを処理することが重要です。
成功パスを直線的に保てるよう、早めにガード節を設けてください。
Promise の所有者を明確にする
tryAsync は Promise を返します。その所有者を明示的に選んでください。
- この関数内で Result を調べる場合は、
awaitを使用します。 - 呼び出し元に
Promise<Result<...>>の所有を委ねる場合は、return tryAsync(...)を使用します。 - 単独の
void tryAsync(...)は使用しないでください。通常の失敗ではErrを含む形で Promise が fulfilled になるため、Promise の reject ハンドラーではその失敗を検出できません。ベストエフォートの操作にも、Result をawaitし、エラー分岐を明示的にログに記録するか無視する、非同期処理の所有者が必要です。 - UI の fire-and-forget コードでは、Result を含む形で fulfilled になる Promise を破棄する前に、結果の表示処理を付与します。例:
void save().then((result) => toastOnError(result, 'Save failed'))。Promise が reject される可能性がある場合は、先にその reject を変換するか、catch してください。
契約を表現する場合はネイティブの try-catch を維持する
従来の try-catch が適切なのは、次のような場合です。
finallyブロックでクリーンアップを行う場合。- ジェネレーターで失敗を Result として返すのではなく、
yieldする必要がある場合。 - フレームワークの境界で、例外を必要なレスポンス形式に直接変換する場合。
- 既知の例外を 1 つだけ捕捉し、それ以外はすべて再スローする場合。
- 周囲のパブリック API が意図的に例外をスローする場合。
未知のプログラミングエラーを汎用的なドメインエラーに変換しないでください。すべての throw を Err に変換すると、誤解を招く再試行可能なエラーの背後にバグが隠れてしまうおそれがあります。
最終確認
- 関数の throw と Result のどちらを使うかという契約が明確です。
- ラッパーが一貫した失敗の意味を扱っています。
- 捕捉した値が、型付きエラー、意図的なフォールバック、または選択的な再スローに変換されています。
- 発生し得る
Err分岐がすべて、名前付きのベストエフォート処理の所有者によって処理、転送、ログ記録、表示、または明示的に無視されています。 - 未知のバグが、適切なクラッシュ処理またはフレームワークのエラー境界に到達します。