【実務・中級編】Dartのコレクションリテラルにおける「if」と「for」の活用:宣言的なデータ構築の極意 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

開発チームの皆さん、コードレビューお疲れ様です。テクニカルリードの私だ。

今日のレビューで、また「命令的なリスト構築」のアンチパターンを見かけたので、この機会にチーム全体の意識をアップデートしておこう。

APIから取得したレスポンスを加工する際や、FlutterのUIコンポーネントツリーを動的に組み立てる際、`if`文や`for`ループのためにわざわざ一時的なミュータブル変数(`List list = [];`)を用意し、`.add()`を連打していないか?

// ❌ 絶滅させるべき「命令的」なリスト構築
List buildActions(bool isAdmin, List permissions) {
final actions = [];

actions.add(const Icon(Icons.home));

if (isAdmin) {
actions.add(const Icon(Icons.admin_panel_settings));
}

for (final permission in permissions) {
actions.add(PermissionBadge(permission));
}

return actions;
}

このコードの何が問題か。
1. 変数がミュータブル(可変)である:スコープ内で状態が変更されるため、認知負荷が高まり、バグの温床になる。
2. 宣言的ではない:「どうやって作るか(How)」の手順が書かれており、「何を作りたいのか(What)」という構造が直感的に見えない。

Dartは、コレクションリテラル(`[]`, `{}`, `{}`)の中に直接制御フロー(`if`, `for`)を埋め込める「Collection If / For」という強力な構文を備えている。これを使えば、イミュータブル(不変)を保ったまま、美しく宣言的なデータ構築が可能になる。

今回は、DartのコンパイラとVMの挙動も踏まえつつ、実務で即座に使える堅牢な設計パターンを伝授しよう。

—

1. コレクションリテラルにおける `if` と `for` の基本思想

Dartのコレクションリテラル内で使われる `if` や `for` は、単なる糖衣構文ではない。これは「式(Expression)」の文脈で評価される。

命令的な `.add()` は文(Statement)であり、手続きを強制する。しかし、コレクションリテラル内の制御フローは「この条件を満たすならこの要素を評価し、展開せよ」というデータ構造の宣言そのものだ。

Dart VMは、このような静的に構造が定まるコレクションを極めて効率的にメモリ上に配置する。一時的なバッファアロケーションを削減し、GC(ガベージコレクション)の負荷を最小限に抑えることができるのだ。

—

2. プロダクションコードで実践する:堅牢なコンポーネント・データ構築

では、実際のフロントエンド開発やAPI連携の現場を想定したコードを見てみよう。権限管理、動的なUIパーツの生成、APIクエリパラメータの構築をすべて「コレクション内包」でエレガントに解決する。

// 模範的なプロダクションコード:イミュータブル&宣言的構築
import ‘package:flutter/material.dart’;

enum UserRole { guest, user, admin, superAdmin }

class UserDashboardData {
final UserRole role;
final List activeFeatures;
final Map metadata;

const UserDashboardData({
required this.role,
required this.activeFeatures,
required this.metadata,
});
}

/// ユーザーの権限と機能フラグに基づき、UIアクションとAPIペイロードを同時に構築する
class DashboardPresenter {
List buildToolbarActions({
required UserDashboardData data,
required bool isNetworkConnected,
}) {
// 全て const または final で完結し、ミュータブルな変数は一切存在しない
return [
// 常に存在する基本ボタン
const IconButton(
icon: Icon(Icons.refresh),
tooltip: ‘更新’,
onPressed: _handleRefresh,
),

// 【Collection If】条件付きで単一の要素を挿入
if (isNetworkConnected)
const IconButton(
icon: Icon(Icons.cloud_done),
tooltip: ‘オンライン’,
onPressed: null,
)
else
const IconButton(
icon: Icon(Icons.cloud_off),
tooltip: ‘オフライン’,
onPressed: _handleReconnect,
),

// 権限に応じた管理者専用ボタン
if (data.role == UserRole.admin || data.role == UserRole.superAdmin) …[
// スプレッド演算子(…)を組み合わせることで、複数要素のグループを一網打尽に挿入可能
const VerticalDivider(),
IconButton(
icon: Icon(Icons.security),
tooltip: ‘セキュリティコンソール’,
onPressed: _openAdminConsole,
),
],

// 【Collection For】リストデータを変換しながら展開
for (final feature in data.activeFeatures)
FeatureChip(featureName: feature),
];
}

/// APIへ送信するペイロード(Map)の動的構築
Map buildApiPayload({
required UserDashboardData data,
required String? optionalTrackingId,
}) {
return {
‘role’: data.role.name,
‘feature_count’: data.activeFeatures.length,

// Map リテラル内でも if は完全に機能する
if (optionalTrackingId != null) ‘tracking_id’: optionalTrackingId,

// ネストされたデータ構造の動的構築
‘client_metadata’: {
‘os’: ‘Dart VM Target’,
for (final entry in data.metadata.entries)
if (entry.value != null) entry.key: entry.value,
},
};
}

void _handleRefresh() {}
void _handleReconnect() {}
void _openAdminConsole() {}
}

class FeatureChip extends StatelessWidget {
final String featureName;
const FeatureChip({required this.featureName, super.key});

@override
Widget build(BuildContext context) => Chip(label: Text(featureName));
}

—

3. この設計がもたらす圧倒的なメリット

① 不変性(Immutability)の担保とCognitive Loadの軽減

変数が `final` または定数として扱われるため、コードのどの行を見ても「この値は途中で書き換えられていない」という絶対的な保証(Referential Transparency)が生まれる。バグの大部分は「意図せぬ状態の書き換え」から生じるため、これを文法レベルで根絶できる。

② スプレッド演算子(`…`)とのシナジー

`if` や `for` の中で複数の要素(リスト)を展開したい場合、スプレッド演算子(Spread operator)を組み合わせることで、直感的なグルーピングが可能になる。
特にFlutterのUI開発において、「特定の条件で複数ウィジェットを挿入したい」という要件は頻出するが、これまでは `.addAll([…])` などの命令的処理に頼るしかなかった。それが今や宣言的に記述できる。

③ Mapリテラルへの応用

これはリスト(List)だけに限らない。Mapリテラル内でも `if` や `for` は完全に機能する(上記の `buildApiPayload` を参照せよ)。
APIリクエストボディやJSONを組み立てる際、「特定のフラグが立っている時だけキーを含める」という処理を、三項演算子や後から `if` で `map.putIfAbsent()` を呼ぶ汚いコードから解放してくれる。

—

4. チーフアーキテクトからの警告:パフォーマンスの罠

最後に、Dart VMの内部構造を知る者としての注意点を伝えておく。

コレクション内包(Collection If/For)は非常に強力だが、「過度に複雑なロジック」をリテラル内に詰め込んではならない。

例えば、リテラルの内部でO(N^2)の複雑なフィルタリングや、重いオブジェクトのインスタンス化を大量に行うような記述をすると、UIのビルドフェーズ(60fps / 120fpsの制約下)でフレームドロップを引き起こす原因になる。

  • NGパターン:コレクションリテラルの中に、可読性を損なうほどの複雑な三項演算子やネストしたループを書く。
  • 正解パターン:複雑な変換ロジックはあらかじめサービスクラスやプライベートメソッドで加工・フィルタリングを済ませておき、UIやリテラル構築のレイヤーでは「純粋な展開とマッピング」に徹する。

—

まとめ

今日から君たちのコードレビューにおいて、リストやマップを構築する際に `List.add()` やミュータブルな変数を使った手続き型のアプローチを見かけたら、こうツッコミを入れてほしい。

「それ、Collection If/Forで宣言的に書けるよね?」と。

Dartという言語のポテンシャルを極限まで引き出し、美しく、堅牢で、保守性の高いコードベースを共に築き上げていこう。次のレビューを楽しみにしている。

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