Dart 制御フロー — 条件分岐とループ
制御フローはコードの脳である — プログラムがどの道を進み、何回繰り返すかを決める。
1. 学べること
- if-else / switch-case(Dart 3 switch 式のプレビューを含む)
- for / while / do-while ループ
- for-in と Iterable の走査
- break / continue とラベル制御
- Bob のシナリオ:DataPipeline のデータフィルタリングロジック
2. 開発者のリアルな物語
(1) 課題:ネストした if-else がコードを保守不能にする
Alice は EC サイトの注文データを処理しており、注文をフィルタリングして分類するために 3 階層にネストした if-else 構造を書いていた。ロジックはこうだ:まず注文ステータスをチェックし、次に金額範囲をチェックし、最後に支払い方法を確認する。300 行の「ピラミッド」コードは、あらゆる変更に対してすべてのロジックを理解することを彼女に強いた。1 つの変更漏れが VIP 注文の割引計算のエラーを招き、1.5 万ドルの直接的な損失につながった。
(2) 解決策:制御フローのベストプラクティス
Dart 3 の switch 式を使ってネストした if-else を置き換え、インデックスベースの反復を for-in に置き換えたことで、コードは 300 行から 80 行に削減され、ロジックは明確で保守しやすくなった。
// 修正前: ネストした if-else のピラミッド
// 修正後: クリーンな switch 式
String classifyOrder(Order order) => switch ((order.status, order.amount)) {
('pending', > 1000) => 'VIP Pending',
('pending', _) => 'Normal Pending',
('shipped', _) => 'In Transit',
('delivered', _) => 'Completed',
_ => 'Unknown',
};
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
(3) 効果
- switch 式で分岐ロジックが一目で明確になり、ネストしたピラミッドが撲滅できる
- for-in による走査がより安全で、境界外エラーを回避できる
- ラベル制御により複雑なループでの break/continue の正確なジャンプが可能
3. if-else 条件分岐
(1) 基本構文
▶ サンプル:if-else の基本的な使い方
void main() {
double orderAmount = 1500.0;
if (orderAmount > 1000) {
print('VIP order - apply discount');
} else if (orderAmount > 500) {
print('Standard order');
} else {
print('Small order');
}
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
▶ サンプル:式としての if-else(三項演算子の代替)
void main() {
int orderCount = 1500;
// 三項演算子
String tier = orderCount >= 1000 ? 'Enterprise' : 'Standard';
// 式としての if-else(Dart 3)
String label = if (orderCount >= 1000) 'Enterprise' else 'Standard';
print('Tier: $tier');
print('Label: $label');
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
| 形式 | 構文 | 値を返すか | Dart バージョン |
|---|---|---|---|
| if 文 | if (cond) { ... } |
いいえ | 全バージョン |
| 三項演算子 | cond ? a : b |
はい | 全バージョン |
| if 式 | if (cond) a else b |
はい | Dart 3.7+ |
4. switch と switch 式
(1) 従来の switch 文
▶ サンプル:switch 文
void main() {
String status = 'shipped';
switch (status) {
case 'pending':
print('Order is waiting for processing');
break;
case 'shipped':
print('Order is in transit');
break;
case 'delivered':
print('Order has been delivered');
break;
default:
print('Unknown status: $status');
}
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
(2) Dart 3 の switch 式
▶ サンプル:switch 式
// switch 式 - 値を返す
String getStatusLabel(String status) => switch (status) {
'pending' => 'Awaiting Processing',
'shipped' => 'In Transit',
'delivered' => 'Completed',
'cancelled' => 'Cancelled',
_ => 'Unknown Status',
};
// ガード付きパターンマッチング
String classifyOrder(double amount) => switch (amount) {
>= 10000 => 'Enterprise',
>= 1000 => 'Premium',
> 0 => 'Standard',
_ => 'Invalid',
};
void main() {
print(getStatusLabel('shipped')); // In Transit
print(classifyOrder(5500)); // Premium
print(classifyOrder(150)); // Standard
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
| 観点 | switch 文 | switch 式 |
|---|---|---|
| 値を返す | いいえ | はい |
| break | 必須 | 不要 |
| 構文 | case x: |
x => |
| Dart バージョン | 全バージョン | Dart 3+ |
| 網羅性チェック | いいえ | はい(シールクラス) |
5. ループ構造
(1) for ループ
▶ サンプル:従来の for ループ
void main() {
// 古典的な for ループ
for (int i = 0; i < 5; i++) {
print('Processing batch ${i + 1}');
}
// カウントダウン
for (int i = 10; i > 0; i -= 2) {
print('Countdown: $i');
}
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
(2) for-in ループ
▶ サンプル:for-in 走査
void main() {
List<String> sources = ['orders.csv', 'products.csv', 'customers.csv'];
// List の for-in
for (final source in sources) {
print('Loading: $source');
}
// Set の for-in
Set<String> uniqueStatuses = {'pending', 'shipped', 'delivered'};
for (final status in uniqueStatuses) {
print('Status: $status');
}
// Map エントリの for-in
Map<String, int> categoryCount = {
'Electronics': 500,
'Clothing': 300,
'Books': 200,
};
for (final entry in categoryCount.entries) {
print('${entry.key}: ${entry.value} products');
}
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
(3) while と do-while ループ
▶ サンプル:while と do-while
void main() {
// while - 条件を先にチェック
int retries = 0;
bool connected = false;
while (!connected && retries < 3) {
print('Connection attempt ${retries + 1}...');
retries++;
connected = retries >= 2; // 成功をシミュレート
}
// do-while - 最低 1 回は実行
int batchSize = 10000;
int processed = 0;
do {
int chunk = (batchSize - processed > 1000) ? 1000 : batchSize - processed;
print('Processing $chunk records...');
processed += chunk;
} while (processed < batchSize);
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
| ループ種別 | 初期チェック | 最低実行回数 | 用途 |
|---|---|---|---|
for |
開始前 | 0 | 回数が既知 |
for-in |
開始前 | 0 | コレクションの走査 |
while |
開始前 | 0 | 条件駆動 |
do-while |
実行後 | 1 | 最低 1 回は実行が必要 |
6. break、continue、ラベル
(1) break と continue
▶ サンプル:break と continue
void main() {
List<double> amounts = [1500, -50, 3200, 0, 890, -100, 2100];
// continue - 無効な注文をスキップ
int validCount = 0;
double totalValid = 0;
for (final amount in amounts) {
if (amount <= 0) continue; // 無効をスキップ
validCount++;
totalValid += amount;
}
print('Valid: $validCount, Total: $totalValid USD');
// break - 最初エラーで停止
for (final amount in amounts) {
if (amount < 0) {
print('Error: Negative amount $amount found!');
break;
}
print('Processing: $amount USD');
}
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
▶ サンプル:ラベル制御
void main() {
// ネストしたループ制御のためのラベル
outer:
for (int batch = 0; batch < 3; batch++) {
for (int record = 0; record < 5; record++) {
if (record == 2 && batch == 1) {
print('Critical error at batch $batch, record $record');
break outer; // 両方のループから抜ける
}
print('Batch $batch, Record $record');
}
}
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
7. Bob のシナリオ:DataPipeline のデータフィルタリング
flowchart TD
A[生データ] --> B{フィルタ条件}
B -->|有効注文| C[for-in で反復]
B -->|無効データ| D[continue でスキップ]
C --> E[集約計算]
E --> F[結果出力]
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
▶ サンプル:注文データのフィルタリングと集約
void main() {
List<Map<String, dynamic>> rawOrders = [
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
{'id': 'ORD-002', 'amount': -50.0, 'status': 'completed'},
{'id': 'ORD-003', 'amount': 3200.0, 'status': 'pending'},
{'id': 'ORD-004', 'amount': 0.0, 'status': 'completed'},
{'id': 'ORD-005', 'amount': 890.0, 'status': 'completed'},
{'id': 'ORD-006', 'amount': 2100.0, 'status': 'cancelled'},
];
double totalRevenue = 0;
int completedCount = 0;
Map<String, double> revenueByStatus = {};
for (final order in rawOrders) {
final amount = order['amount'] as double;
final status = order['status'] as String;
// 無効な注文をスキップ
if (amount <= 0) continue;
// キャンセルされた注文をスキップ
if (status == 'cancelled') continue;
// ステータス別に売上を集約
revenueByStatus[status] = (revenueByStatus[status] ?? 0) + amount;
// 完了をカウント
if (status == 'completed') {
completedCount++;
totalRevenue += amount;
}
}
print('=== DataPipeline Filter Report ===');
print('Completed orders: $completedCount');
print('Total revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
for (final entry in revenueByStatus.entries) {
print(' ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
}
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
8. 完全なサンプル:DataPipeline バッチ処理コントローラー
// ============================================
// DataPipeline バッチ処理コントローラー
// すべての制御フロー構造を使用
// ============================================
class BatchProcessor {
final int batchSize;
int processedCount = 0;
int skippedCount = 0;
double totalRevenue = 0;
BatchProcessor({this.batchSize = 1000});
String classifyAmount(double amount) => switch (amount) {
>= 10000 => 'Enterprise',
>= 1000 => 'Premium',
> 0 => 'Standard',
_ => 'Invalid',
};
void processBatch(List<Map<String, dynamic>> orders) {
int batchNum = 0;
for (int i = 0; i < orders.length; i += batchSize) {
batchNum++;
final end = (i + batchSize < orders.length) ? i + batchSize : orders.length;
final batch = orders.sublist(i, end);
print('\n--- Batch $batchNum (${batch.length} records) ---');
for (final order in batch) {
final id = order['id'] as String?;
final amount = order['amount'] as double?;
final status = order['status'] as String?;
// 無効なレコードをスキップ
if (id == null || amount == null || status == null) {
skippedCount++;
continue;
}
if (amount <= 0) {
skippedCount++;
continue;
}
final tier = classifyAmount(amount);
if (status == 'completed') {
processedCount++;
totalRevenue += amount;
print(' $id: \$${amount.toStringAsFixed(2)} USD [$tier]');
} else if (status == 'pending') {
print(' $id: PENDING - \$${amount.toStringAsFixed(2)} USD [$tier]');
} else {
skippedCount++;
}
}
}
}
void printSummary() {
print('\n=== Processing Summary ===');
print('Processed: $processedCount orders');
print('Skipped: $skippedCount records');
print('Revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
}
}
void main() {
final orders = <Map<String, dynamic>>[
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
{'id': 'ORD-002', 'amount': 15500.0, 'status': 'completed'},
{'id': 'ORD-003', 'amount': -50.0, 'status': 'completed'},
{'id': 'ORD-004', 'amount': 890.0, 'status': 'pending'},
{'id': 'ORD-005', 'amount': 3200.0, 'status': 'completed'},
{'id': 'ORD-006', 'amount': 0.0, 'status': 'cancelled'},
];
final processor = BatchProcessor(batchSize: 3);
processor.processBatch(orders);
processor.printSummary();
}
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
出力:
--- Batch 1 (3 records) ---
ORD-001: $1500.00 USD [Premium]
ORD-002: $15500.00 USD [Enterprise]
--- Batch 2 (3 records) ---
ORD-004: PENDING - $890.00 USD [Standard]
ORD-005: $3200.00 USD [Premium]
=== Processing Summary ===
Processed: 3 orders
Skipped: 3 records
Revenue: $20200.00 USD
❓ よくある質問
Q: switch 文で break は必須ですか? A: はい、Dart の switch 文では、空でない各 case は break、return、throw、continue のいずれかで終わる必要があります。フォールスルーしません。ただし、空の case はフォールスルーできます。
Q: switch 式の
_とは何ですか? A:_はワイルドカードパターンで、残りのすべてのケースにマッチし、switch 文の default に相当します。Dart 3 では default の代わりに_を使うことが推奨されています。
Q: for-in でコレクションを変更できますか? A: いいえ。for-in 走査中にコレクションを変更(要素の追加/削除)すると、ConcurrentModificationError が発生します。変更が必要な場合は、まず変更を収集し、後で適用してください。
Q: いつ for ではなく while を使うべきですか? A: 反復回数が不確定な場合(例:ネットワーク応答を待つ場合)は while を使います。回数が既知の場合や走査するコレクションが明確な場合は for/for-in を使います。
Q: ラベルは実際の開発で頻繁に使われますか? A: 頻繁ではありません。ラベル付き break/continue は主にネストしたループでの正確な制御に使われます。ほとんどの場合、メソッドの抽出や高階関数の使用によるリファクタリングで深いネストを避けられます。
Q: Dart 3 の switch 式は複数値マッチに対応していますか? A: はい。
|を使って複数のパターンを組み合わせられます。例:'pending' | 'processing' => 'In Progress'。
Q: do-while と while でパフォーマンスの違いはありますか? A: 知覚できるパフォーマンスの違いはありません。選択はセマンティクスに依存します。ループ本体を最低 1 回実行する必要があるかどうかです。
📖 まとめ
- if-else は最も基本的な条件分岐。Dart 3 は if 式構文をサポートする。
- switch 式(Dart 3)は switch 文よりも簡潔で安全、網羅性チェックに対応する。
- 4 つのループ種別には異なる用途がある:for(既知の回数)、for-in(コレクション走査)、while(条件駆動)、do-while(最低 1 回)。
- break はループを抜け、continue は現在の反復をスキップし、ラベルはネストしたループでのより正確なジャンプを可能にする。
- DataPipeline では continue を使って無効データをフィルタリングし、switch 式で注文のティアを分類する。
📝 練習問題
- 基礎(難易度 ⭐):for-in を使って 5 件の注文金額を含む List を走査してください。合計と平均を計算し、金額が 0 以下の注文をスキップします。
- 中級(難易度 ⭐⭐):switch 式を使って注文ステータスの分類器を実装し、(ステータス, amount) の組み合わせに応じて異なる処理優先度ラベルを返してください。
- 挑戦(難易度 ⭐⭐⭐):N 件ずつバッチ処理するページネーション対応バッチ処理器を実装し、break による中断(重大なエラー発生時)と continue によるスキップ(無効レコード)に対応させ、最終的な処理統計レポートを出力してください。