【実務・中級編】DartのNull安全と「共変性(Covariance)」:Listが引き起こす型安全性の崩壊と対策 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

Dartの型安全は「完璧」か?― 共変性が引き起こすランダムクラッシュの正体

DartのSound Null Safetyは、コンパイル時にNull参照を排除する強力な盾です。しかし、この盾をいとも簡単に貫通させ、実行時に`TypeError`を突きつけてくる「型システムの死角」が存在することを知っていますか?

それが「共変性(Covariance)」という、Dartの柔軟性がもたらす諸刃の剣です。

今日は、なぜ`List`があなたの堅牢な設計を崩壊させ、なぜ`List`や`Iterable`への厳格な移行が必要なのかを、Dartの内部構造の視点から紐解きます。

—

1. なぜ「共変性」がNull安全を脅かすのか

Dartにおいて、`List`は共変(Covariant)です。これは、`List`が`List`のサブタイプとして扱われることを意味します。直感的には正しいように見えますが、これが型安全性を破壊するトリガーになります。

崩壊のメカニズム

以下のコードを見てください。一見、何も問題がないように見えますが、実行時には致命的なエラーが発生します。

void main() {
List strings = [‘Dart’, ‘Flutter’];

// 共変性により、List は List として扱える
List objects = strings;

// ここで型システムの崩壊が起きる
// コンパイルは通るが、実行時に「Stringのリストにintを入れる」という矛盾が発生
objects.add(100);

// 最後にここを通った瞬間に死ぬ
print(strings[2].length);
}

何が起きているのか?
Dart VMは、このコードをコンパイルする際、`objects.add(100)`が物理的なメモリ領域(`List`のバッキングストア)に対して不整合を起こすことを完全には静的解析できません。実行時に`List`のチェックが行われますが、その時点では手遅れです。

これが、APIから受け取ったデータを`dynamic`として扱い、それを`List`にキャストして使い回すという「フロントエンドの安易な実装」が引き起こす、プロダクションでのクラッシュの正体です。

—

2. 堅牢な設計への転換:`dynamic`からの脱却

「APIのレスポンスが不確定だから`dynamic`を使うしかない」という言い訳は、もはや通用しません。Dartが提供する型システムを正しく活用し、コンパイル時安全性を最大化しましょう。

推奨される設計パターン:`Type Assertion` と `Collection Constraints`

API連携の境界(Boundary)で、データを「守る」ための設計パターンを紹介します。

/// 堅牢なデータ変換層
class UserListMapper {
/// dynamicをそのまま流さず、明示的に検証して型を確定させる
static List fromJson(dynamic data) {
if (data is! List) return [];

// 厳格なフィルタリングとキャスト
return data.whereType().toList();
}
}

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`の活用: `cast()`よりも遥かに安全です。`cast`は実行時まで変換を遅延させますが、`whereType`は即座に型安全なコレクションを構築します。
3. VMの最適化: 型が確定した`List`は、Dart VMがインライン化やメモリレイアウトの最適化を行いやすくなり、パフォーマンスも向上します。

—

3. パフォーマンスと型安全のバランス:`Iterable`の賢い選択

頻繁にデータを操作するコンポーネント設計では、不用意に`List`へ変換せず、`Iterable`をインターフェースとして活用してください。

  • List: 要素へのランダムアクセスが頻発する場合にのみ使用する。
  • Iterable: データ変換やフィルタリングのパイプラインではこちらを選択する。

`Iterable`は、`List`よりも柔軟で、かつ「不変(Immutable)」に近い振る舞いを強制しやすいため、副作用による型崩壊を防ぐ効果があります。

—

結論:コードレビューで意識すべきこと

今後、チームのコードレビューで`List`や`as List`という記述を見かけたら、こう指摘してください。

> 「このキャストは、共変性の隙間を突いて実行時例外を誘発するリスクがある。境界値で`whereType`を使って型を確定させるか、モデル層で適切にマッピングしてほしい」

Dartの型システムは、使いこなせば最強の武器になります。`dynamic`への依存を断ち切り、型に厳格なアーキテクチャを構築することこそが、中・大規模開発における「真の保守性」を担保する唯一の道です。

さあ、あなたのコードから`dynamic`を排除しましょう。それが、Dartの真の力を引き出す第一歩です。

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