【実務・中級編】Null許容型と『List.cast()』の危険な関係:実行時例外を回避する型安全な変換術 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

実行時例外の爆弾を解体せよ:Dartの『List.cast()』とNull安全の深淵

Dartの型システムは、Sound Null Safetyの導入によって劇的に堅牢になりました。しかし、我々が日常的に扱うJSONデータや外部APIからのレスポンス処理において、いまだに「実行時例外」という名の地雷が埋め込まれています。

特に、`List.cast()` を安易に使用することは、コンパイル時に検知できない「型不整合の爆弾」をプロダクション環境に持ち込む行為に等しい。本稿では、Dart VMの内部構造と型判定の挙動を理解し、安全かつエレガントにリストを変換する極意を伝授します。

—

1. なぜ `List.cast()` は危険なのか

多くのエンジニアが犯す過ちは、`List` を受け取った際に、以下のようにキャストすることです。

// 危険:型安全性を破壊する「魔法の呪文」
final rawList = [1, 2, null, 4]; // List
final safeList = rawList.cast();
// ここまではエラーにならない
safeList.add(5);
// しかし、後続の処理で `null` にアクセスした瞬間に `TypeError` が発生する

`List.cast()` は、Dart VMにおいて「Lazy Cast(遅延キャスト)」として扱われます。リストの全要素を走査して型チェックを行うのではなく、「要素にアクセスした瞬間に型チェックを強制する」という設計思想です。これはパフォーマンス上は有利ですが、コンポーネント設計においては「どこで例外が飛ぶか予測不可能」という最悪の保守性を生みます。

—

2. 実務で推奨される「安全なフィルタリング」パターン

外部APIのレスポンスなど、信頼できないデータソースを扱う場合、`cast` ではなく「データ生成時の厳格なフィルタリング」を行うのが鉄則です。

ベストプラクティス:型指定付きの `whereType()`

Dartの `Iterable` に備わっている `whereType()` は、単なるフィルタリングではなく、内部的に `is` チェックを伴う型安全な変換を保証します。

final List rawData = [10, “20”, 30, null, 40];

// 推奨:nullを排除し、指定した型のみを抽出する
// 実行時エラーを未然に防ぐ、最もクリーンな手法
final List cleanList = rawData.whereType().toList();

print(cleanList); // [10, 30, 40]

なぜ `whereType` が最強なのか

1. 型ガードの自動適用: `is` 演算子が内部で実行されるため、コンパイラが型を推論し、後続のコードで `null` チェックが不要になります。
2. 計算量の最適化: 一度のイテレーションでフィルタリングと型変換を完結させるため、`cast()` を経由して後から例外を発生させるコストよりも遥かに安上がりです。

—

3. 非同期API連携における「防衛的プログラミング」

WebエンジニアとしてAPI連携を行う際、最も避けるべきは「APIの構造変化でUI全体がクラッシュすること」です。以下のパターンをモジュール化しておくと、大規模開発での強固な基盤となります。

extension SafeListParsing on List? {
/// null許容のListを、確実に安全なリストに変換する
List safeCast() {
// nullの場合は空リストを返却し、クラッシュを防ぐ
if (this == null) return [];

// 型安全なフィルタリングを実行
return this!.whereType().toList();
}
}

// 使用例
void main() {
final dynamic response = [null, “Apple”, “Banana”, 123];

// 型安全に文字列リストだけを抽出
final List fruits = (response as List).safeCast();

print(fruits); // [“Apple”, “Banana”]
}

—

4. チーフアーキテクトからの忠告:パフォーマンスとメモリの真実

Dart VMにおいて、`List.cast()` は新しいメモリ領域を確保せず、既存のリストへの「View(参照)」を構築します。一方で、`whereType().toList()` は新しいリストを生成します。

一見すると `cast()` のほうがメモリ効率が良さそうに見えますが、「実行時例外を補足するためのコスト」と「バグ調査に費やす開発者の時間」を考慮すれば、`whereType` による生成コストは極めて微々たるものです。

  • 小規模なリスト: `whereType` を使い、型安全を優先する。
  • 数万件の巨大リスト: そもそもDart側で処理すべきか再考する(API側でフィルタリングさせるか、ストリーム処理を検討する)。

結論

Dartの型システムを掌握するとは、「いつ、どこで型検査を行うか」をコンパイラに任せず、自分自身で制御することです。

`List.cast()` は、型安全性が完全に保証されていると確信できる場合(例:内部的なデータ変換の最終ステップ)以外、コードベースから排除してください。代わりに `whereType()` を標準採用することで、あなたの書くコードは驚くほど堅牢になり、深夜のデバッグから解放されるはずです。

型安全とは、単なる規約ではなく、プロダクトを守るための「防壁」です。今日からその防壁を、一段と強固なものにアップデートしてください。

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