こんにちは。Dartの深淵を覗き込み、日夜VMの最適化や言語仕様の策定に向き合っている者です。
今日は、多くのエンジニアが「なんとなく」で使いがちなSound Null Safetyと、それをDI(依存性の注入)の文脈でどう制御するかという、非常に重要なトピックについてお話ししましょう。
DartのNull安全は単なるチェック機能ではありません。コンパイラが「このメモリ領域には必ず値が入っている」と数学的に保証するための、型システムによる最強の防具です。これとDIが組み合わさったとき、どのような設計思想を持つべきか。一緒に紐解いていきましょう。
—
1. なぜ「Null許容型」をDIで扱うのが危険なのか?
DIコンテナ(ProviderやRiverpodなど)は、アプリケーションの「心臓部」に値を注入する仕組みです。ここで「Null許容型(`Type?`)」を安易に許すと、以下のような悲劇が起こります。
- 実行時エラーの温床: 「値が入っているはず」という思い込みが、`!`(強制アンラップ)の乱用を招く。
- 型安全性の崩壊: コンパイラが「Nullかもしれない」と警告するため、コードの至る所で `if (value != null)` というボイラープレート(定型コード)が増殖する。
Dartにおいて `null` とは、単なる値ではなく「まだ何も決まっていない、あるいは存在しない」という状態の宣言です。DIにおいて「未初期化」はバグそのもの。これを型レベルでどう封じ込めるかが、プロの設計です。
—
2. 実践:Null安全なDI設計のゴールデンルール
ProviderやRiverpodでDIを行う際、守るべきは「NonNull(非Null)で定義し、必要なタイミングで確実に注入する」という原則です。
悪い例:Null許容型による先延ばし
// 良くないパターン:どこかで初期化されることを祈る設計
final userRepositoryProvider = Provider
// 使う場所で毎回チェックが必要になる
final user = ref.watch(userRepositoryProvider)?.getUser();
これでは、`UserRepository`が本当に必要なのか、それとも「たまたま存在しないだけ」なのかがコードから読み取れず、保守性が著しく低下します。
良い例:Late Finalによる確実な初期化
DIコンテナのライフサイクルを利用し、`late` や初期化プロバイダーで「必ず存在する」ことを保証しましょう。
// 良いパターン:初期化されるまでアクセスさせない
final userRepositoryProvider = Provider
// 初期化ロジック。ここで失敗したら例外を投げるべき
return UserRepositoryImpl();
});
—
3. 「初期化されていない」を防ぐためのテクニック
特にFlutterでよくある「アプリ起動時に設定値を読み込む」というケース。ここで `late` を使うのは勇気が要りますよね。そんな時は、「準備完了状態(AsyncValue)」をDIの境界線に置くのがDart流です。
Riverpodでの解決策:`FutureProvider` を使う
// 設定情報のプロバイダー
final configProvider = FutureProvider
// 非同期で設定を読み込む。完了するまでProviderは「読み込み中」状態になる
return await loadConfigFromDisk();
});
// UI側での安全なアクセス
ref.watch(configProvider).when(
data: (config) => Text(config.apiKey), // ここでは確実にconfigが存在する
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text(‘Error!’),
);
このように、「Nullかもしれない」という状態を「読み込み中かもしれない」という状態に昇華させるのです。これがSound Null Safety環境下での最も健全な設計です。
—
4. 陥りやすい罠:`late` と `!` の誘惑
最後に、多くの初学者が陥る罠について。
1. `late` の過信: `late` はコンパイラを黙らせるための魔法ではありません。「実行時には絶対に代入されている」という誓約書です。誓約を破れば `LateInitializationError` というクラッシュが待っています。
2. `!` (null assertion) の乱用: 「ここは絶対nullじゃないはず!」と `!` を使うのは、コンパイラを信頼していない証拠です。もし `!` が必要な場面が多いなら、それは設計段階で依存関係が正しく解決されていないサインだと考えてください。
—
まとめ:Dartを掌握するために
DartのSound Null Safetyは、皆さんのコードをより堅牢にするための「設計の補助輪」です。
- Null許容型を使うのは、本当に「値がないこと」が仕様である場合だけにする。
- DIコンテナには、初期化済みのオブジェクトだけを流す(AsyncValueを活用する)。
- `!` や `late` に頼る前に、コードの依存グラフを見直す。
ここをクリアすれば、あなたのDartコードは劇的に洗練されます。コンパイラの警告は「敵」ではなく、あなたの設計の「欠陥」を教えてくれる親切なパートナーです。
さあ、自信を持って型システムと対話してください。その先に、本当に堅牢なアプリケーションの世界が待っています。応援していますよ!