こんにちは。Dartの深淵へようこそ。
Dartは進化を続けていますが、Dart 3で導入された「パターンマッチング」と「sealed class」の組み合わせは、この言語を単なる「UI記述言語」から「堅牢なドメインロジックを構築するための強力なツール」へと昇華させました。
今日は、多くのエンジニアが「なんとなく」使っているこれらの機能を、コンパイラの視点から紐解き、あなたのコードを「絶対に壊れない」ものにするための極意を伝授します。
—
1. なぜ「sealed class」を使うのか?:コンパイラの視点
一般的なクラス定義では、そのクラスがどこで継承されているかをコンパイラはすべて把握できません。しかし、`sealed` キーワードをつけると、Dartコンパイラは「このクラスのサブクラスは、このファイル(ライブラリ)内で定義されたものだけである」と確信します。
これが何を意味するか分かりますか?
「網羅性チェック」が可能になるということです。
// ドメインモデル例:決済状態
sealed class PaymentStatus {}
class Pending extends PaymentStatus {}
class Success extends PaymentStatus {}
class Failed extends PaymentStatus {}
このように定義すると、Dartのコンパイラは「`PaymentStatus` の状態は、この3つ(Pending, Success, Failed)以外には絶対に存在しない」という強固な前提を保持します。
—
2. switch式:制御構文から「値の変換器」へ
Dart 3から導入された `switch` 式は、文(Statement)ではなく「式(Expression)」です。つまり、結果を直接変数に代入したり、関数の戻り値として返したりできます。
網羅性チェックの恩恵
もし、将来的に「Refund(返金)」という新しい状態を追加したとしましょう。
class Refund extends PaymentStatus {} // 新しく追加!
この瞬間、既存の `switch` 式を使っている箇所で、Dartコンパイラが「網羅されていません」と真っ赤な警告を出します。 これこそが、大規模開発における最強の防衛線です。実行時エラー(Runtime Error)が発生する前に、開発時にコンパイラが教えてくれるのです。
String getStatusMessage(PaymentStatus status) {
return switch (status) {
Pending() => ‘処理中です…’,
Success() => ‘支払いが完了しました!’,
Failed() => ‘決済に失敗しました。’,
// ここで網羅性チェックが働きます!
// Refund() を追加し忘れると、コンパイルすら通りません。
};
}
—
3. 実践:ドメインモデルを堅牢にする「パターンマッチング」
単に型を分けるだけでなく、データの中身を分解(デストラクト)して活用するのがDart流の賢い書き方です。
sealed class Result {}
class Success extends Result {
final String data;
Success(this.data);
}
class Error extends Result {
final int errorCode;
Error(this.errorCode);
}
// switch式で分解しながら処理する
String handleResult(Result result) => switch (result) {
Success(data: var d) => ‘成功: $d’,
Error(errorCode: 404) => ‘見つかりませんでした’,
Error(errorCode: var code) => ‘エラーが発生しました: $code’,
};
ここで注目してほしいのは、`Error` 型をさらに `errorCode` の中身で条件分岐させている点です。これを「パターンマッチング」と呼びます。データ構造の形をそのままコードに落とし込めるため、複雑な `if-else` の迷路に迷い込むことはもうありません。
—
4. 陥りやすい罠と解決策
初学者がよくやるミスに、「`default` ケースを書いてしまう」ことがあります。
❌ やってはいけないこと
switch (status) {
case Pending(): …
case Success(): …
default: … // これを書くと網羅性チェックが無効化されます!
}
`default` を書くと、コンパイラは「あ、あとは任せるよ」と判断し、新しいサブクラスを追加しても警告を出さなくなります。`sealed class` を使うなら、`default` は封印してください。 すべてのケースを明示的に記述するのが、堅牢なコードへの近道です。
—
まとめ:Dartを掌握するということ
Dart 3の `sealed class` と `switch` 式は、単なる便利な機能ではありません。「コードの変更が、どこに影響を与えるかをコンパイラに強制的に意識させる」ための設計手法です。
1. sealed class で状態を制限する。
2. switch 式 で網羅性を担保する。
3. パターンマッチング でデータの詳細を取り出す。
この3ステップを意識するだけで、あなたの書くDartコードの信頼性は劇的に向上します。大規模なチーム開発になればなるほど、この「コンパイラが守ってくれる」感覚は強力な武器になります。
ここをマスターすれば、もう「UIの裏側」で発生するロジックの不整合に怯えることはありません。さあ、次はどんなドメインモデルを構築しますか?あなたのコードがより堅牢で美しいものになることを、心から応援しています。