Dartの型安全は「完璧」か?― 共変性が引き起こすランダムクラッシュの正体
DartのSound Null Safetyは、コンパイル時にNull参照を排除する強力な盾です。しかし、この盾をいとも簡単に貫通させ、実行時に`TypeError`を突きつけてくる「型システムの死角」が存在することを知っていますか?
それが「共変性(Covariance)」という、Dartの柔軟性がもたらす諸刃の剣です。
今日は、なぜ`List
—
1. なぜ「共変性」がNull安全を脅かすのか
Dartにおいて、`List
崩壊のメカニズム
以下のコードを見てください。一見、何も問題がないように見えますが、実行時には致命的なエラーが発生します。
void main() {
List
// 共変性により、List
// ここで型システムの崩壊が起きる
// コンパイルは通るが、実行時に「Stringのリストにintを入れる」という矛盾が発生
objects.add(100);
// 最後にここを通った瞬間に死ぬ
print(strings[2].length);
}
何が起きているのか?
Dart VMは、このコードをコンパイルする際、`objects.add(100)`が物理的なメモリ領域(`List
これが、APIから受け取ったデータを`dynamic`として扱い、それを`List
—
2. 堅牢な設計への転換:`dynamic`からの脱却
「APIのレスポンスが不確定だから`dynamic`を使うしかない」という言い訳は、もはや通用しません。Dartが提供する型システムを正しく活用し、コンパイル時安全性を最大化しましょう。
推奨される設計パターン:`Type Assertion` と `Collection Constraints`
API連携の境界(Boundary)で、データを「守る」ための設計パターンを紹介します。
/// 堅牢なデータ変換層
class UserListMapper {
/// dynamicをそのまま流さず、明示的に検証して型を確定させる
static List
if (data is! List) return [];
// 厳格なフィルタリングとキャスト
return data.whereType
}
}
void main() {
final rawData = [‘Alice’, 100, ‘Bob’]; // APIレスポンスを想定
// 不純物を除去し、List
final safeList = UserListMapper.fromJson(rawData);
print(safeList); // [Alice, Bob]
print(safeList.runtimeType); // List
}
なぜこれが「美しい」のか
1. 境界での封じ込め: 不確定要素(`dynamic`)をビジネスロジックの深部に持ち込ませません。
2. `whereType
3. VMの最適化: 型が確定した`List
—
3. パフォーマンスと型安全のバランス:`Iterable`の賢い選択
頻繁にデータを操作するコンポーネント設計では、不用意に`List`へ変換せず、`Iterable`をインターフェースとして活用してください。
- List: 要素へのランダムアクセスが頻発する場合にのみ使用する。
- Iterable: データ変換やフィルタリングのパイプラインではこちらを選択する。
`Iterable`は、`List`よりも柔軟で、かつ「不変(Immutable)」に近い振る舞いを強制しやすいため、副作用による型崩壊を防ぐ効果があります。
—
結論:コードレビューで意識すべきこと
今後、チームのコードレビューで`List
> 「このキャストは、共変性の隙間を突いて実行時例外を誘発するリスクがある。境界値で`whereType`を使って型を確定させるか、モデル層で適切にマッピングしてほしい」
Dartの型システムは、使いこなせば最強の武器になります。`dynamic`への依存を断ち切り、型に厳格なアーキテクチャを構築することこそが、中・大規模開発における「真の保守性」を担保する唯一の道です。
さあ、あなたのコードから`dynamic`を排除しましょう。それが、Dartの真の力を引き出す第一歩です。