Dart 3の真骨頂:Switch式で「有限ステートマシン」をエレガントに構築する
こんにちは。Dartの深淵を覗き込んでいる皆さん、ようこそ。
今回は、多くの開発者が「なんとなく」使いがちな`switch`文を、Dart 3で導入された強力な「パターンマッチング」と「switch式」を駆使して、堅牢な有限ステートマシン(FSM)へと昇華させるテクニックを伝授します。
if-elseのネスト地獄から抜け出し、コンパイラが「網羅性(Exhaustiveness)」を保証してくれる設計を手に入れましょう。
—
1. なぜ「Switch式」がFSMに最適なのか?
従来のFSM実装では、巨大なクラス構造や、無数のif文が必要でした。しかし、Dart 3のswitch式は違います。
- 網羅性チェック: 状態を一つでも定義し忘れると、コンパイラが即座にエラーを吐いて教えてくれます。
- データとロジックの分離: 状態を「データ(Enum/Sealed Class)」として定義し、ロジックを「パターンマッチング」で記述するため、非常にクリーンです。
まずは、シンプルな「注文ステート」を例に見てみましょう。
—
2. 実装:堅牢なステートマシンを構築する
まずは状態を定義します。Dart 3では`sealed class`を使うことで、「このクラスを継承できるのは同じファイル内のこれらだけ」と限定し、switchでの完全網羅を強制できます。
// 状態の定義
sealed class OrderState {}
class Pending extends OrderState {}
class Paid extends OrderState {}
class Shipped extends OrderState {}
class Delivered extends OrderState {}
// 状態遷移を管理する関数
OrderState nextState(OrderState current) => switch (current) {
Pending() => Paid(),
Paid() => Shipped(),
Shipped() => Delivered(),
// もしDeliveredの状態を書き忘れると、ここでコンパイルエラー!
Delivered() => Delivered(),
};
ここがポイント!
- `sealed class`: 状態の「型」を定義します。これにより、switch文は「この型の派生クラスをすべて網羅しているか?」を計算時に判断できます。
- `switch (current)`: これは文(statement)ではなく式(expression)です。戻り値として直接次の状態を返せるため、コードが劇的に短くなります。
—
3. パターンマッチングで「条件付き遷移」を実現する
実務では、単に状態が変わるだけでなく、「価格が足りているか?」といった条件分岐が絡みますよね。ここでもDartのパターンマッチングが輝きます。
sealed class OrderState {}
class Pending extends OrderState { final int amount; Pending(this.amount); }
class Paid extends OrderState {}
// 遷移ロジックに「ガード」を加える
OrderState process(OrderState current) => switch (current) {
// Pendingかつamountが1000以上の時だけPaidへ
Pending(amount: >= 1000) => Paid(),
// それ以外のPendingはそのまま
Pending() => current,
Paid() => Paid(),
};
陥りやすいエラー:網羅性の欠如
もし `Pending()` のガード条件を書き漏らしたり、新しい状態クラスを追加したのにswitch側を更新しなかった場合、Dartコンパイラは「非網羅的なswitch式です」と警告します。これがバグを未然に防ぐ「Dartの守護神」の力です。
—
4. なぜこれが「クリーンアーキテクチャ」なのか
このアプローチは、「状態というデータ」と「遷移という振る舞い」を明確に切り離しています。
1. 単一責任の原則: 遷移ロジックがひとつのswitch式に集約されるため、コードの追跡が容易になります。
2. 型安全性: `sealed class`とパターンマッチングにより、実行時に「予期せぬ状態」が発生する余地をコンパイルレベルで排除できます。
3. 可読性: 状態遷移図をそのままコードに落とし込んでいるかのような直感的な構造になります。
—
5. 初学者がつまずきやすいポイントと攻略法
- 「なぜ `default` を使ってはいけないの?」
- 初学者はつい `default` を書いて網羅性チェックを回避しがちです。しかし、FSMにおいて`default`を使うことは「異常系を隠蔽する」ことに他なりません。網羅性チェックをエラーにさせることこそが、Dart流のデバッグ手法です。
- 「switch文とswitch式、どっちを使うべき?」
- 戻り値を返すなら `switch (…)` 式を。副作用(ログ出力やDB更新など)のみを行う場合は `switch (…) { case …: … }` 文を使いましょう。
—
最後に:Dartを掌握するということ
Dartの美しいところは、「コンパイラが開発者の思考を補助してくれる」点にあります。
今回紹介したFSMの書き方は、複雑なビジネスロジックを抱えるFlutterアプリにおいても、非常に高い保守性を発揮します。ぜひ、次回の開発で「`if-else`を消し去る」ことから始めてみてください。
ここをクリアすれば、あなたはもうDartの基本をマスターしたも同然です。さあ、次はどんな複雑な状態遷移を、このエレガントな手法で解き明かしますか?
—
Dartのコードは常に「読みやすさ」と「堅牢さ」の狭間にあります。そのバランスを制御できるようになった時、あなたはDartエンジニアとして一段上のステージに立っているはずです。