【入門編】Null安全と『Result型』の設計:例外を投げずにNull許容型を扱う関数型アプローチ – Dart コア文法・オブジェクト指向・Null安全解析バイブル

やあ。Dartの世界へようこそ。
FlutterやDart VMの深淵に触れていると、「Null」という存在がどれほどエンジニアを悩ませ、そして実行時のクラッシュを生むトリガーになるかを痛感させられます。

今日は、Dartの「Sound Null Safety(健全なNull安全)」を正しく守りつつ、さらに一歩進んだ「例外を投げない堅牢なアーキテクチャ」について話をしよう。

「Nullチェック地獄」から抜け出し、関数型プログラミングのエッセンスを取り入れた設計手法を、一緒にマスターしていこう。

—

1. なぜ「Nullを返す」ことが危ういのか?

DartのNull安全は、コンパイル時に「ここにはNullが入り得るぞ」と厳格に教えてくれる素晴らしい仕組みです。しかし、API設計で「失敗した時にnullを返す」というアプローチを多用すると、呼び出し側は常に `if (result == null)` というチェックを強いられることになります。

// ありがちな「Nullで失敗を表現する」設計
User? fetchUser(String id) {
if (database.isOffline) return null; // 失敗がNull
return User(id: id);
}

// 呼び出し側:Nullチェック地獄の始まり
final user = fetchUser(‘123’);
if (user != null) {
print(user.name);
} else {
// ここでエラーハンドリングが必要だが、なぜ失敗したか分からない
}

このコードの問題点は、「失敗の理由(原因)」が捨てられていることと、「Nullの確認を忘れるリスク」が常に残ることです。

2. 救世主『Result型』の概念

ここで、関数型プログラミングの知見を借りよう。成功(Success)と失敗(Failure)を明示的に保持する「Result型」というコンテナを使うんだ。

Dartの強力な `sealed class` を使えば、型安全かつ網羅的に状態をハンドリングできる。

// Result型の定義(成功と失敗を型で分ける)
sealed class Result {
const Result();
}

class Success extends Result {
final T value;
const Success(this.value);
}

class Failure extends Result {
final Exception error;
const Failure(this.error);
}

なぜこれが強力なのか?

この `sealed` クラスを使うと、Dartのコンパイラは `switch` 文で「全パターン(成功か失敗か)」を処理しているかを強制的にチェックしてくれる。これぞ、Dart VMが誇る型システムの真骨頂です。

3. 実践:Nullチェックから解放されるコード

では、先ほどの `fetchUser` を `Result` 型を使って書き換えてみよう。

Result fetchUser(String id) {
try {
// 成功時はSuccessに包む
return Success(User(id: id));
} catch (e) {
// 失敗時は理由と共にFailureに包む
return Failure(Exception(‘ユーザーが見つかりません: $e’));
}
}

void main() {
final result = fetchUser(‘123’);

// switch式(Dart 3.0以降の強力な武器)
final message = switch (result) {
Success(value: final user) => ‘ようこそ、${user.name}さん!’,
Failure(error: final e) => ‘失敗しました: ${e.toString()}’,
};

print(message);
}

ここで何が起きているのか?

1. 網羅性チェック: もし `Failure` の処理を書き忘れたら、Dartはコンパイルエラーを出して教えてくれる。
2. 情報の保持: `Failure` には例外情報が含まれているため、デバッグが極めて容易になる。
3. Nullの消滅: このコードの中に `null` は一文字も登場しない。すべてが型によって管理されているんだ。

4. 初学者が陥りやすい罠

多くの人がやりがちなミスとして、「Result型の中にさらにNullを許可してしまう」ことがあります。

// 悪い例:中身がNullかも知れないResult
Result fetchUser(…) // これでは元の木阿弥だ!

`Result` を使うときは、`T` には非Null型(Null許容型ではない型)を入れるのが鉄則だよ。失敗したときは `Failure` を返し、成功したときは必ず値が存在する状態にする。これこそが、Null安全を極めた設計なんだ。

結びに:Dartを掌握するということ

Null安全は単なる「エラーを防ぐための足かせ」ではなく、「コンパイラに設計の意図を正しく伝えるための対話」です。

Result型のような設計パターンを取り入れることで、君の書くコードは「Nullチェックというノイズ」から解放され、ビジネスロジックそのものを語り始めるはずです。

もし現場で「Nullが怖い」と感じたら、まずは `sealed class` で状態を切り出すことから始めてみて。Dart VMが君の設計を完璧に守ってくれることを実感できるはずだよ。

さあ、次はどんな深いコードの海へ潜りに行こうか? またいつでも相談してくれ。

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