Instruction file imported from yukimiyake0607/book_review (
.cursor/rules/error-handling.mdc). Copyright stays with the author.
エラー設計(sealed AppException)
失敗を握りつぶさず「網羅的に分岐できる型」として扱う。
型
sealed class AppException implements Exception(ユーザー向けmessageを持つ)。- 具体は
final class:NetworkException/NotFoundException/ServerException/ValidationException/UnknownException。
Exception と Error を分ける
| 失敗の性質 | 型 | 扱い |
|---|---|---|
| 起こりうる失敗(通信断・不正な入力・破損した保存データ) | Exception |
境界で AppException に型付けし、UI で見せる |
| コードのバグ(型の取り違え・null 参照・同梱漏れ) | Error |
捕まえない。グローバルハンドラ(core/error/global_error_handler.dart)まで伝播させる |
流れ(層ごとの責務)
| 層 | 責務 |
|---|---|
| infrastructure | 外部由来の失敗を境界で AppException へ変換して送出(HTTP は mapDioException、アセット/ストレージは各実装が直接送出) |
| domain | 入力制約違反は ValidationException を送出(値オブジェクトの parse) |
| presentation | AsyncValue.error から受け取り、ユーザー向けメッセージへ(AppErrorView) |
ルール
- 上位層に生の例外を漏らさない。リポジトリの公開メソッドから出る失敗は必ず
AppException。 - catch は
on Exception。on Object/ on 句なしの catch は書かない(Errorを巻き込み、バグが「予期しないエラー」に化ける)。 Errorはどの層でも捕まえない。ログのために一度受ける場合も rethrow する。- 外部入力の失敗は
Exception側へ寄せる。素のasキャストは型不一致をTypeError(Error 系)にしてしまうため書かない。- DTO は json_serializable の
checked: true(build.yaml)で生成し、fromJsonの失敗をCheckedFromJsonExceptionにする。 jsonDecodeの結果やSharedPreferencesの値は型を確かめ、想定外ならFormatExceptionを送出する。
- DTO は json_serializable の
AsyncValue.guardにはonlyAppException(core/error/guard_policy.dart)を渡す。既定はObjectを捕まえるため、渡さないとErrorがAsyncValue.errorに載ってグローバルハンドラへ届かない。- 外部入力は「信頼できない値」として扱い、domain へ入れる前に
parseを通す(Rating.parse)。永続化データも外部入力に含む。 - ユーザー提示メッセージは
AppException.message(日本語)を単一の出所にする。 - 新しい失敗種別が必要なら enum ではなく
AppExceptionのfinal classを追加する(sealed の網羅性を活かす)。
唯一の例外:破損した永続データ
ReviewLocalStore.read() だけは失敗を握りつぶし、該当キーごと削除して空リストで再開する。ユーザー操作では復旧できず、出し続けてもアプリを使えなくするだけのため。握りつぶす対象は FormatException と ValidationException だけ(CheckedFromJsonException は FormatException に変換してから受ける)。それ以外の Exception はキーを消さず伝播させ、Error も伝播させる。書き込みの失敗は握りつぶさない(UnknownException を送出。成功扱いにすると再起動時に消失が発覚する)。
