【入門編】Dartの「パターンマッチング」におけるガード句の評価順序とパフォーマンスへの影響 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartでの開発を楽しんでいますか?

他の言語からDartに入門された方から、よくこんな質問をいただきます。
「Dart 3で入ったパターンマッチング、`switch`式の中で条件分岐が書ける`when`(ガード句)ってすごく便利だけど、これって一体どういう順番で評価されて、パフォーマンスにどう影響するの?」

さすが、実務を見据えた鋭い着眼点ですね!ここをしっかり理解しておくと、ただ動くだけでなく、「CPUに無駄な負荷をかけない、美しく高速なコード」を書けるようになります。

今日は、DartのコンパイラやVMが裏側でどう動いているのかという少しディープな視点も交えつつ、ガード句の秘密を優しく、そして徹底的に解説していきますね。ここをクリアすれば、Dartの制御構文はもうバッチリマスターできますよ!

—

1. ガード句(`when`)の基本と「評価順序」の正体

まずは基本のおさらいからいきましょう。
Dart 3のパターンマッチングでは、`switch`ケースの後に `when (条件式)` というガード句を付与できます。これにより、型や構造が一致した(マッチした)あとに、さらに細かい追加条件で絞り込むことができるんです。

まずは、次のようなシンプルなコードを見てみてください。

String classifyNumber(int number) {
return switch (number) {
// パターン1: 0の場合
0 => ‘ゼロです’,

// パターン2: 正の数かつ偶数の場合(ガード句を使用)
int n when n > 0 && n.isEven => ‘正の偶数です’,

// パターン3: その他の整数
int n => ‘その他の整数です’,
};
}

void main() {
print(classifyNumber(0)); // ゼロです
print(classifyNumber(4)); // 正の偶数です
print(classifyNumber(-2)); // その他の整数です
}

ここで重要なポイントがあります。Dart VM(仮想マシン)やコンパイラは、このコードを上から順に評価していきます。

1. まず、上から順に「パターン(構造と型)」が一致するかをテストします。
2. パターンが一致した瞬間、そのケースに付随する `when` ガード句の条件式が評価されます。
3. ガード句が `true` を返せばそのアーム(矢印の右側)が実行され、`false` であれば次のケースへと評価が流れます。

⚠️ 陥りやすい罠:上から順に評価されることの意識漏れ

「パターンがマッチしてから `when` が評価される」というこの順番を忘れてしまうと、予期せぬバグを生むことがあります。例えば、次のようなコードはコンパイルエラーや論理エラーを引き起こしやすい典型例です。

String checkValue(dynamic val) {
return switch (val) {
// ❌ 危険な順序の例
int n => ‘整数です’,
int n when n > 100 => ‘100より大きい整数です’, // 到達不能コード(Dead Code)になる!
_ => ‘その他’,
};
}

最初の `int n` にすべての整数が吸い込まれてしまうため、2番目の `int n when n > 100` には絶対に制御が到達しません(Dartの静的解析器が「Unreachable code」として警告を出してくれます)。
ガード句を使いたい場合は、「より厳しい条件(特定のガード付き)」を上へ、「一般的な条件」を下へ配置するのが鉄則です。

—

2. パフォーマンスへの影響:ガード句に「重い処理」を書いてはいけない理由

さて、ここからが本題です。
「パターンが一致したあとに `when` が評価される」ということは、ガード句の中身は実行時に毎回評価されるということになります。

ここで、うっかり計算コストの高い処理(重い関数呼び出しや複雑なオブジェクト走査など)をガード句の中に書いてしまうと、アプリケーションのパフォーマンスに悪影響を及ぼします。

イメージ図でその動きを追ってみましょう。

[入力値]
│
▼
[パターンマッチング評価] (軽量)
│
├─► マッチ不成立 ──► 次のケースへ
│
└─► マッチ成立
│
▼
[ガード句 (when) の評価]
│ (※ここに重い処理があると、マッチするたびに実行される!)
├─► false ──► 次のケースへ
│
└──► true ──► 処理を実行!

悪い例:ガード句の中で重い計算や非同期的な操作を行っているケース

例えば、次のようなコードはパフォーマンスの観点からあまり推奨できません。

// 模擬的な「とても重い計算処理」
bool isHeavyCalculatedValid(int value) {
// 実際には重いアルゴリズムや外部参照があると仮定
print(‘⚠️ 重い計算が走っています…’);
return value % 3 == 0;
}

String processItem(int item) {
return switch (item) {
// ❌ ガード句の中で重い関数を呼んでいる
int n when isHeavyCalculatedValid(n) => ‘条件Aに一致’,
int n => ‘その他’,
};
}

もし `item` が条件Aに何度もマッチしなかった場合、Dart VMはパターンマッチを試みるたびに `isHeavyCalculatedValid(n)` を評価し続けることになります。これがループ内などで大量に呼び出されると、フレームレートのドロップ(カクつき)やCPU使用率の高騰を招く原因になります。

—

3. 現場で使える!パフォーマンス最適化のプラクティス

では、計算コストの高い条件分岐を行いたいときは、どう書くのが正解なのでしょうか?
チーフアーキテクトからの実践的なアドバイスは以下の2点です。

① 事前に計算(キャッシュ)しておく

パターンマッチに入る前にあらかじめ重い計算を済ませておき、その結果(フラグなど)をスイッチの対象にする、あるいはローカル変数に保持するのが最もシンプルで効果的な最適化です。

String optimizedProcessItem(int item) {
// ✔️ 事前に計算コストを払う、または結果を保持する
final bool isValid = isHeavyCalculatedValid(item);

return switch ((item, isValid)) {
(int n, true) => ‘条件Aに一致 (値: $n)’,
(int n, false) => ‘その他 (値: $n)’,
};
}

このように、条件判定のプリミティブな要素をタプル(`Item, isValid`)としてまとめ、純粋なパターンマッチだけで分岐を解決させれば、ガード句内の無駄な関数実行を防ぐことができます。

② 軽量な条件をガード句の左側に置く(短絡評価の活用)

ガード句の中で複数の条件を `&&`(AND)や `||`(OR)で繋ぐ場合は、計算コストが低いもの、または失敗しやすいものを左側に書くようにしましょう。

Dartの論理演算子は短絡評価(Short-circuit evaluation)を行います。つまり、`&&` の左側が `false` であれば、右側の評価はスキップされます。

// ✔️ 効率的なガード句の書き方
int n when n > 0 && isLightweightCheck(n) => ‘…’,

もし `n > 0` が `false` であれば、右側の `isLightweightCheck(n)` は実行されません。これにより、不要な関数呼び出しをスマートに回避できます。

—

まとめ

いかがでしたでしょうか? 本日の重要なポイントをまとめます。

1. 評価順序: Dartの `switch` は上から順に評価され、パターンが一致したあとに `when` ガード句が評価される。
2. 順序の注意: 一般的な条件を上に書くと、下のガード付き条件に到達しなくなる(到達不能コードになる)ので、厳しい条件を上に書く。
3. パフォーマンス: ガード句の中身は実行時に毎回評価されるため、重い処理を直接書くのは避ける。必要に応じて事前計算や短絡評価を活用する。

Dartの制御構文とパターンマッチングは、コンパイラの最適化とも深く結びついている非常に強力な機能です。仕組みの裏側まで少し意識を向けるだけで、あなたの書くコードはワンランク上の「プロフェッショナルなコード」に生まれ変わります。

ぜひ今日の知見を実際のプロジェクトで活かしてみてくださいね。それではまた!

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