こんにちは。Dartの世界へようこそ。
Dartの「Null安全(Sound Null Safety)」は、単なるエラー回避の仕組みではありません。これは「コンパイラがコードの実行時におけるNull参照を数学的に排除する」という、極めて強力な保証です。
今回は、Null安全下での「Getter/Setter」の設計という、一見地味ですが、実はアプリケーションの堅牢性を左右する最重要テーマについてお話しします。
—
1. なぜ「直接の代入」を避けるべきなのか?
他の言語から来た方は、ついつい `public` な変数を公開しがちです。しかし、Dartにおいて「状態」を直接公開することは、Null安全の恩恵を自ら捨てるようなものです。
例えば、こんなコードを見てください。
class UserProfile {
String? nickname; // 外部から直接アクセス可能
}
これでは、どこで `null` が代入されるか制御できません。DartのNull安全は「変数がNullになり得るか」を静的に解析します。`nickname` が Null であることを許容してしまった瞬間、その値を使うすべての場所で「本当にNullじゃない?」というチェック(`! ` や `?.`)を強制され、コードが汚染されてしまいます。
—
2. カプセル化による「状態の守り方」
外部には「値が存在すること」を保証しつつ、内部では状態の変化を制御する。これがプロのGetter/Setter設計です。
基本戦略:Private変数と公開Getter
Dartのプライベート変数(`_` プレフィックス)を使い、外部公開用のGetterを定義します。
class UserProfile {
// 1. 内部状態は private にして外部から隠す
String? _nickname;
// 2. コンストラクタで初期化を強制する
UserProfile(String initialNickname) : _nickname = initialNickname;
// 3. Getter: 外部には「確実に値がある」ことを保証して返す
// Null安全の観点から、もしNullならデフォルト値を返すのが定石
String get nickname => _nickname ?? “名無しの権兵衛”;
// 4. Setter: バリデーションを挟んでNullを排除する
set nickname(String value) {
if (value.trim().isEmpty) {
throw ArgumentError(“ニックネームは空にできません!”);
}
_nickname = value;
}
}
この設計の「魂」
- Getterの役割: 内部で `null` であっても、外部には「デフォルト値」という救済措置を提示することで、利用側のコードで `!` を使わせない。
- Setterの役割: 値が代入される入り口で門番(バリデーション)を行い、不正な値や `null` が内部状態に入り込むのを物理的に防ぐ。
—
3. 陥りやすい「Null安全の罠」
初学者がよくやるミスは、「初期化忘れ」です。Dartは「非Null型(`String`など)の変数は、宣言時またはコンストラクタで必ず初期化しなければならない」という厳しいルールがあります。
悪い例:初期化をサボる
class Config {
late String apiKey; // late を使えばコンパイルは通るが…
}
`late` は「後で必ず初期化するから信じてくれ!」というコンパイラへの誓約書です。しかし、初期化する前に `apiKey` にアクセスすると、実行時に `LateInitializationError` が発生し、アプリはクラッシュします。
解決策: コンストラクタで引数として受け取るか、デフォルト値を与える。
class Config {
// コンストラクタで初期化を強制するのが最も安全
final String apiKey;
Config(this.apiKey);
}
—
4. チーフアーキテクトからのアドバイス:DartVMの視点
Dartの素晴らしい点は、「開発時の安全性」と「実行時の最適化」が両立していることです。
Null安全が正しく設計されたコードは、Dart VM(仮想マシン)にとって「この変数は絶対にNullにならない」という強力なヒントになります。これにより、VMは頻繁に行われるNullチェックを省略し、極限まで最適化されたマシンコードを生成します。
つまり、「安全なコードを書くこと」は「高速なアプリを作ること」に直結しているのです。
—
まとめ:ここをマスターすれば大丈夫!
1. フィールドは `_` で隠す: 外部に直接公開しない。
2. Getterで「値がある保証」を作る: `??` 演算子を活用し、呼び出し側にNullを意識させない設計にする。
3. Setterで門番を置く: 不正な値やNullの侵入を許さない。
4. `late` は最後の手段: できる限りコンストラクタで初期化を完結させる。
Null安全は、最初は窮屈に感じるかもしれません。ですが、これは皆さんが書くコードの「品質」を守るための最強の防壁です。この防壁を正しく設計できるようになれば、あなたはもう初心者ではありません。
ぜひ、自分の書くクラスを見直して、「本当にこの変数は外部から書き換えられても安全か?」を自問自答してみてください。それが、Dartを掌握する第一歩です。
また何か詰まったら、いつでも聞きに来てくださいね。一緒に最高の実装を追求しましょう!