【実務・中級編】Dartのパターンマッチングにおける「失敗」の伝播:if-caseとswitch式の挙動の違い – Dart コア文法・オブジェクト指向・Null安全解析バイブル

Dartパターンマッチングの深淵:`if-case`と`switch`式の「失敗」がコードの寿命を決める

Dart 3で導入されたパターンマッチングは、単なる糖衣構文ではない。これは言語仕様の根幹に関わる「静的解析と実行時のフロー制御の融合」だ。

我々Dartエンジニアが理解すべきは、「パターンがマッチしなかったとき、プログラムの実行コンテキストで何が起きるか」という点に尽きる。特に、`if-case`と`switch`式における「失敗の伝播」を混同することは、バグの温床であり、アーキテクチャの劣化を招く。

今日は、その差異をVM内部の挙動から紐解き、実務で戦える「堅牢な設計」を叩き込む。

—

1. 「無視」と「遮断」:制御構文のイデオロギー

まず、この2つの根本的な性質を定義しよう。

  • `if-case` (制御フローとしての分岐): パターンマッチングは「ガード」として働く。マッチしなかった場合、それは「単なるスキップ」だ。実行フローは次の一行へ進む。
  • `switch`式 (式としての評価): これは「値の変換」だ。マッチしないことは「不完全な定義(網羅性の欠如)」とみなされ、ランタイムで `SwitchExpressionError` が発生する。

なぜ `switch` 式の「失敗」は許されないのか

`switch`式は、結果を返すことを前提とした「式」である。Dartの健全な型システムにおいて、値が確定しない式は定義できない。もしマッチしない可能性があるなら、それはコンパイラが「網羅性(Exhaustiveness)」を強制する理由そのものだ。

—

2. 実務で直面する「失敗の伝播」:具体例

APIレスポンスのハンドリングを例に見てみよう。

// 良くない例:switch式で予期せぬレスポンスを握りつぶす設計
final status = response.statusCode;
final result = switch (status) {
200 => ‘Success’,
404 => ‘Not Found’,
// 500等のエラーが来た瞬間、SwitchExpressionErrorでアプリがクラッシュする
};

一方、`if-case`はどうか。

// 安全な例:if-caseによるプログレッシブなフィルタリング
if (response case {‘data’: final data, ‘status’: 200}) {
return process(data);
}
// マッチしなかった場合? ここでログを吐くか、デフォルト値を返すかのフォールバックが可能
logger.warning(“Unexpected response: $response”);
return DefaultValue();

—

3. パフォーマンスとVMの挙動:隠れたコスト

ここでアーキテクトとしての視点を提供する。Dart VMのAOTコンパイルにおいて、`switch`式は非常に効率的な「ジャンプテーブル」や「決定木」へと最適化される。これは、定数時間(O(1)〜O(log n))で分岐を決定できる非常に強力な構造だ。

対して、`if-case`を多用すると、それは個別の条件評価の積み重ね(線形探索)となり、条件が複雑になればなるほど、命令キャッシュの効率が落ちる可能性がある。

設計の指針:

1. 網羅性が担保できる有限の集合(Enumやsealed class)なら、迷わず `switch` 式を使え。
2. 動的なJSON解析や、条件が複雑で「マッチしなければ何もしなくていい(あるいは別のロジックへ流したい)」なら `if-case` を選べ。

—

4. プロダクションコード:守りの設計パターン

APIクライアントにおける「Result型」を用いたパターンマッチングのベストプラクティスを提示する。

sealed class RemoteResult {}
class Success extends RemoteResult { final T value; Success(this.value); }
class Failure extends RemoteResult { final Object error; Failure(this.error); }

// コンポーネント設計での活用
Widget build(BuildContext context) {
final state = fetchState();

// switch式で「状態の漏れ」をコンパイラに検知させる(設計の堅牢性)
return switch (state) {
Success(value: final data) => Text(data.title),
Failure(error: final err) => ErrorWidget(err),
// 状態が追加されたらここでコンパイルエラーになるため、メンテナンス性が極めて高い
};
}

—

結びに:なぜ「言語の重み」を知る必要があるのか

私がチーフアーキテクトとしてコードレビューをする際、もっとも重視するのは「その書き方が、将来の変更に対してどう応答するか」だ。

`switch`式は「変更が来たら必ずここを直せ」というコンパイラからの強いメッセージであり、`if-case`は「ここは柔軟なフィルタリングの場である」という宣言だ。

Dart 3のパターンマッチングを使いこなすということは、単に便利な構文を並べることではない。プログラムの「状態の遷移」を、コンパイラのガードレールの上でいかにエレガントに描くかという知的な営みなのだ。

君たちが書くその一行が、将来のバグを防ぎ、チームの時間を救う。その責任を胸に、今日も美しいコードを書いてほしい。

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