例外という名の「隠れた副作用」を捨てろ:Dart 3 パターンマッチングで実現する堅牢なResult型設計
Dartの`try-catch`は、一見便利だが、実は「関数シグネチャによる契約」を破壊する爆弾だ。メソッドが何を投げるか(Throw)を静的に解析するのは不可能であり、呼び出し側は常に「見えないエラー」に怯えなければならない。
Dart 3のパターンマッチングは、単なる構文糖衣ではない。これは、「エラーを型として戻り値に含め、呼び出し側に明示的に処理を強制する」という関数型プログラミングのパラダイムを、Dartの型システムに持ち込むための強力な武器だ。
今回は、プロダクション環境で例外を排除し、堅牢性を担保する「Result型」の実装と、その裏側にあるコンパイラレベルの最適化について解説する。
—
1. なぜ「例外」は避けるべきなのか
例外は「予期せぬ事態」には適しているが、APIのレスポンスやユーザー入力のバリデーションといった「業務上想定される失敗」には不向きだ。
- 型安全性の欠如: `Future
`と書かれていても、実際には例外でクラッシュする可能性がある。 - 制御フローの破綻: `try-catch`を多用するとコードの可読性が下がり、`catch`を忘れた瞬間にアプリは死ぬ。
これらを解決するために、`Result`型(成功値かエラー値のどちらか片方のみを持つコンテナ)を導入する。
—
2. 究極のResult型実装:sealed class と switch 式
Dart 3の `sealed class` を使えば、コンパイラが「全てのパターンが網羅されているか(Exhaustiveness checking)」を厳密にチェックしてくれる。これこそが、バグを未然に防ぐ最強の防波堤だ。
/// 成功と失敗を表現するResult型
sealed class Result
const Result();
}
class Success
final T value;
const Success(this.value);
}
class Failure
final E error;
const Failure(this.error);
}
// 使用例:API通信のシミュレーション この設計の肝は、呼び出し側が `switch` 式を使って処理を強制される点にある。 void main() { // コンパイラが Success と Failure の両方を処理したか厳密にチェックする print(message); ここが技術的ポイント: — 「ラップされたオブジェクトを生成するのはオーバーヘッドではないか?」という疑問を持つ鋭い諸君へ。 1. アロケーションの最適化: Dart VMのAOTコンパイラは、`sealed class`による型階層を静的に理解している。`switch`式は、単なる条件分岐ではなく、VM内部で高度に最適化されたジャンプテーブルへとコンパイルされるため、実行時のオーバーヘッドは極めて小さい。 — プロダクションでResult型を導入する際は、以下のルールを徹底してほしい。 Dart 3のパターンマッチングは、単なる構文の進化ではない。それは、「予測不能なランタイム」を「制御可能な型システム」へと押し込めるための設計革命だ。 コードレビューにおいて、もし誰かが `try-catch` を広範囲で使っていたら、それは「例外という名の隠れた副作用」を放置しているサインだ。この `Result` 型の設計パターンを注入し、チームのコードベースから「想定内のエラー」によるクラッシュを根絶せよ。 型安全とは、コンパイラを味方にすることだ。今日も最高にクリーンなコードを書いてくれ。
Result
if (id <= 0) {
return Failure(Exception('Invalid ID: $id'));
}
return Success('User: DartMaster');
}
---
3. パターンマッチングによる「強制力のある」ハンドリング
final result = fetchUserData(-1);
final message = switch (result) {
Success(value: final val) => ‘Success: $val’,
Failure(error: final err) => ‘Error occurred: ${err.toString()}’,
};
}
もし将来的に `Loading` や `Unauthorized` といった状態を `Result` に追加した場合、コンパイラは `switch` 式でそれらを処理していないと即座に警告を発する。つまり、「修正漏れによるクラッシュ」が物理的に発生しなくなる。4. パフォーマンスの真実:なぜこれが高速なのか
2. インライン化: パターンマッチングが単純な場合、コンパイラは該当する処理を呼び出し元にインライン展開する。例外を投げてスタックトレースを生成するコストと比較すれば、Result型によるオブジェクト生成コストなど誤差に近い。5. 実務で「勝つ」ための運用戦略
まとめ