【実務・中級編】Dartの「switch式」で複数のパターンを「or(|)」で結合する際の可読性向上 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

コードレビューの現場から:その冗長な `switch` 文、コンパイラを欺いていませんか?

フロントエンド開発、特にFlutterを用いた堅牢なUIコンポーネント設計や、複雑な非同期APIのステート管理において、私たちのコードベースは「状態の分岐」の嵐にさらされています。

先日、あるジュニアエンジニアからこんなプルリクエストが上がってきました。

// ❌ よくある冗長なswitch文(アンチパターン)
String getActionDescription(UserRole role) {
switch (role) {
case UserRole.admin:
case UserRole.owner:
return ‘システム全体の管理権限があります。’;
case UserRole.editor:
case UserRole.writer:
return ‘コンテンツの作成・編集が可能です。’;
case UserRole.guest:
return ‘閲覧のみ可能です。’;
}
}

一見して動くコードですが、テクニカルリードの視点からは「技術的負債の芽」に見えます。冗長な `case` のフォールスルー、ブレイクの書き忘れリスク、そして何より「式(Expression)」ではなく「文(Statement)」であるために不変な(`final` な)データフローを構築できないという致命的な弱点があります。

Dart 3以降、私たちには論理和パターン(`|` operator)を用いた `switch` 式という強力な武器が与えられています。この仕様を骨の髄まで理解し、プロダクションコードを圧倒的に美しく、かつ安全にスケールさせる方法を伝授します。

—

Dart 3 `switch` 式と論理和パターン(`|`)のメカニズム

Dartのコンパイラ(CFA: Control Flow Analysis)は、`switch` 式を評価する際、網羅性チェック(Exhaustiveness checking)を行います。ここで複数のケースを論理和パターン `|` で結合すると、AST(抽象構文木)上で単一の「合流点」として最適化されます。

// ⭕ Dart 3の真骨頂:論理和パターンを用いたswitch式
String getActionDescription(UserRole role) => switch (role) {
UserRole.admin | UserRole.owner => ‘システム全体の管理権限があります。’,
UserRole.editor | UserRole.writer => ‘コンテンツの作成・編集が可能です。’,
UserRole.guest => ‘閲覧のみ可能です。’,
};

この記法が優れているのは、単に行数が減るからではありません。「どの状態とどの状態が同じビジネスロジックを共有しているのか」というドメインの意図が、コードの構造として直感的に同期される点にあります。

—

【実践】API連携とUIコンポーネント設計での応用パターン

Webフロントエンドやクロスプラットフォーム開発において、非同期通信の状態管理(Loading, Success, Error, 以及そのサブ状態)をハンドリングするシーンは頻出します。

ここでは、APIからのレスポンスステータスとユーザー権限を組み合わせた、より実践的で堅牢なコンポーネント設計のプロダクションコードを提示します。

import ‘package:flutter/material.dart’;

// 1. ドメインモデルの定義
enum ApiStatus { idle, loading, success, unauthorized, forbidden, serverError, timeout }

sealed class UserRole {
const UserRole();
}
class Admin extends UserRole { const Admin(); }
class PremiumUser extends UserRole { const PremiumUser(); }
class FreeUser extends UserRole { const FreeUser(); }

/// 2. ネットワークエラーと権限を安全に解決するデザインコンポーネント
class ApiStateFeedbackBanner extends StatelessWidget {
final ApiStatus status;
final UserRole role;

const ApiStateFeedbackBanner({
Key? key,
required this.status,
required this.role,
}) : super(key: key);

@override
Widget build(BuildContext context) {
// switch式と論理和(|)、ガード節(when)を組み合わせた高度なパターンマッチング
final (String message, Color backgroundColor) = switch ((status, role)) {
// ローディング状態は権限に関わらず共通
(ApiStatus.loading, _) => (‘データを同期中…’, Colors.blue.shade100),

// サーバーエラー系は一律で保守的なメッセージを返す
(ApiStatus.serverError | ApiStatus.timeout, _) =>
(‘一時的な通信エラーが発生しました。時間をおいて再試行してください。’, Colors.red.shade100),

// 認証・認可エラー:管理者と一般ユーザーで文言を厳密に分岐
(ApiStatus.unauthorized | ApiStatus.forbidden, Admin()) =>
(‘特権セッションが切れました。再ログインが必要です。’, Colors.orange.shade200),

(ApiStatus.unauthorized | ApiStatus.forbidden, _) =>
(‘このコンテンツにアクセスする権限がありません。’, Colors.orange.shade100),

// 成功時:ユーザーの階層に応じたUI表現
(ApiStatus.success, Admin() || PremiumUser()) =>
(‘プレミアム機能が有効です。’, Colors.green.shade100),

(ApiStatus.success, FreeUser()) =>
(‘無料プランを利用中です。広告なしプランにアップグレードしませんか?’, Colors.grey.shade200),

// デフォルト(Dart 3の網羅性チェックにより、ここには理論上到達しないが安全策として)
_ => (‘不明なステータスです。’, Colors.grey.shade300),
};

return Container(
padding: const EdgeInsets.all(16.0),
color: backgroundColor,
child: Text(message, style: const TextStyle(fontWeight: FontWeight.bold)),
);
}
}

このコードが優れている理由(アーキテクチャの視点)

1. レコード(Records)との融合: `(status, role)` のように複数の変数をタプル(レコード)としてまとめ、それを一度にパターンマッチングさせています。これにより、ネストした `if-else` 地獄を完全に駆逐しています。
2. 網羅性の保証: もし将来 `ApiStatus` に新しいステータスが追加された場合、Dartのコンパイラは即座にビルドエラーを吐き出し、「このケースが処理されていません」と教えてくれます。バグの温床である「書き忘れ」をコンパイル時に完全に封殺します。
3. 副作用のない純粋なマッピング: UIの表示内容(ViewModel的な責務)が純粋な式として評価されるため、テストコードの記述が極めて容易になります。

—

言語の仕様を深く理解し、コンパイラの最適化機構と静的解析の恩恵を最大限に引き出すこと。それこそが、プロダクションの品質を担保するエンジニアの矜持です。

明日からのコードレビューでは、無駄に長い `switch` 文を見かけたら、胸を張ってこの論理和パターンへのリファクタリングを提案してください。あなたのコードベースは、より堅牢で美しいものへと進化するはずです。

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