【実務・中級編】Dart 3のパターンマッチングで実装する「Result型」:例外処理を型安全なフローに変える – Dart コア文法・オブジェクト指向・Null安全解析バイブル

なぜ「例外」は現代のDartにおいて「悪」なのか?

多くのエンジニアが犯す過ちは、`try-catch` を「予期せぬエラーを防ぐための魔法の杖」だと勘違いしていることだ。

Dartの `Exception` や `Error` は、呼び出しスタックを巻き戻し、意図しない制御フローの分岐を引き起こす。特に非同期処理が絡む Flutter アプリケーションにおいて、深い階層で発生した例外を適切に補足し損ねれば、ユーザーの目の前でアプリはクラッシュする。

我々が目指すべきは、「例外を発生させない」ことではなく、「失敗を型として定義し、呼び出し元に強制的に処理させる」ことだ。 Dart 3で導入されたパターンマッチングと代数的データ型(ADT)の概念を使えば、これを極めてエレガントに実現できる。

—

堅牢な Result 型の設計

まずは、この設計の根幹となる `Result` 型を定義しよう。ここで重要なのは、`sealed class` を使うことだ。これにより、コンパイラは `switch` 式において「網羅性(Exhaustiveness)」を保証する。

/// 成功と失敗を表現する Result 型
/// sealed class を使うことで、switch での漏れをコンパイル時に検知できる
sealed class Result {
const Result();
}

class Success extends Result {
final S value;
const Success(this.value);
}

class Failure extends Result {
final F error;
const Failure(this.error);
}

なぜこれが「最強」なのか?

もしあなたが `switch` で `Success` しか処理せず `Failure` を書き忘れた場合、Dartコンパイラは即座にエラーを吐く。これにより、「エラーハンドリングを忘れる」というバグを物理的に排除できる。

—

実践:API連携における「型安全なフロー」

現場でよくある、APIコールを抽象化したサービス層の例を見てみよう。

// 失敗の定義も enum や sealed class で構造化するのがプロの作法
sealed class NetworkError {
const NetworkError();
}

class ServerError extends NetworkError {}
class TimeoutError extends NetworkError {}

class ApiClient {
Future> fetchData() async {
try {
// 本来は Dio などのライブラリを使用
final response = await Future.delayed(
const Duration(seconds: 1),
() => “Payload Data”
);
return Success(response);
} catch (_) {
// 例外をキャッチして「型」に変換する
return Failure(ServerError());
}
}
}

—

パターンマッチングによる「宣言的」な制御フロー

呼び出し側(UIコンポーネントやRepository層)では、もはや `try-catch` を書く必要はない。`switch` 式を用いて、戻り値の型に合わせて処理を分岐させる。

void handleApiResult() async {
final client = ApiClient();
final result = await client.fetchData();

// switch 式で完全に制御する
final message = switch (result) {
Success(value: final data) => ‘Success: $data’,
Failure(error: ServerError _) => ‘Error: サーバーが死んでる’,
Failure(error: TimeoutError _) => ‘Error: タイムアウト’,
};

print(message);
}

パフォーマンスへの配慮

「パターンマッチングは遅いのではないか?」という懸念を持つかもしれない。しかし、Dart VM における `switch` 式は、特に `sealed class` を対象とした場合、型判別(Type Test)の最適化が極めて強力だ。従来の `if-else` や `instanceof` の羅列に比べ、VMは型インデックスに基づくジャンプテーブルに近い最適化を行うため、計算コストは最小限に抑えられる。

—

テクニカルリードからの提言

この設計を導入する際に、必ず守るべきルールが一つある。

「ドメインの境界を超えて例外を伝播させないこと」

Repository 層から UI 層まで、すべての関数のシグネチャを `Future>` に統一せよ。これを徹底すれば、コードベース全体から「いつ発生するかわからない例外」が駆逐される。

  • 保守性の向上: 関数定義を見るだけで、どのような失敗パターンが想定されているか、開発者は即座に理解できる。
  • 認知負荷の低減: `try-catch` のネスト地獄から解放され、コードがフラットで読みやすくなる。

Dart 3のパターンマッチングは、単なるシンタックスシュガーではない。それは、「予測不能なランタイムエラー」を「コンパイル時に制御可能なデータ」へと昇華させるための強力な武器だ。

今日からあなたのプロジェクトで、例外を「投げる」のをやめ、Result を「返す」設計に変えてほしい。それが、世界最高峰のアーキテクチャへの第一歩だ。

タイトルとURLをコピーしました