ソフトウェア開発において、想定外のエラーや例外的な状況を安全に処理することは、信頼性の高いアプリケーションを作るための必須要件です。
Dartは、JavaやC++に似た伝統的な try-catch / on 構文による例外処理機構を持つと同時に、開発時の事前条件検証を行う assert、そして現代的な関数型アプローチである Result型パターン(代数的データ型とパターンマッチの活用) を両方サポートしています。
この章では、Dartにおける適切なエラーハンドリング戦略を体系的に学びます。
try、catch、on、finally とカスタム例外Dartでは、任意のオブジェクトを例外として throw できますが、通常は Exception または Error を実装したクラスをスローします。
on ExceptionType: 特定の例外型だけを指定してキャッチします。catch (e, stackTrace): 例外オブジェクトとスタックトレースを取得します。rethrow: キャッチした例外を処理した後、再度上位の呼び出し元へ再スローします。finally: 例外の有無にかかわらず、最後に必ず実行されるクリーンアップブロックです。class ValidationException implements Exception {
final String message;
ValidationException(this.message);
@override
String toString() => 'ValidationException: $message';
}
void validateAge(int age) {
if (age < 0) {
throw ValidationException('年齢は0以上である必要があります');
}
}
void main() {
try {
print('年齢チェック開始');
validateAge(-5);
} on ValidationException catch (e) {
print('検証エラーをキャッチ: $e');
} catch (e, stack) {
print('予期せぬエラー: $e\n$stack');
} finally {
print('検証処理終了(finallyブロック実行)');
}
}dart run custom_exception_demo.dart年齢チェック開始 検証エラーをキャッチ: ValidationException: 年齢は0以上である必要があります 検証処理終了(finallyブロック実行)
assert による開発時のバグ検知assert(条件, メッセージ) は、開発中(デバッグモード)にプログラムの不変条件や関数の前提条件を検証するための文です。
リリース(AOTコンパイルや本番ビルド)時にはコード自体が完全に無視(除去)される ため、本番環境の実行速度に影響を与えません。
void setPercentage(double rate) {
// 開発時のみチェックされ、不正なら AssertionError を発生させる
assert(rate >= 0.0 && rate <= 1.0, 'rate は 0.0 〜 1.0 の間である必要があります');
print('設定されたレート: ${(rate * 100).toStringAsFixed(1)}%');
}
void main() {
setPercentage(0.75);
}dart run assert_demo.dart設定されたレート: 75.0%
例外を throw するアプローチは、関数の呼び出し側がエラー処理を忘れてしまうリスクがあります。
Dart 3の sealed クラス(第8章参照)を活用すると、RustやSwiftのような Result型 を型安全に自作でき、エラーハンドリングをコンパイル時に強制できます。
// 1. sealed クラスで Result 型を定義
sealed class Result<T, E> {
const Result();
}
class Success<T, E> extends Result<T, E> {
final T value;
const Success(this.value);
}
class Failure<T, E> extends Result<T, E> {
final E error;
const Failure(this.error);
}
// 2. 例外をスローせず Result 型を返す関数
Result<int, String> divide(int a, int b) {
if (b == 0) {
return const Failure('0 で割ることはできません');
}
return Success(a ~/ b);
}
void main() {
final res1 = divide(10, 2);
final res2 = divide(10, 0);
for (final res in [res1, res2]) {
// switch式で Success と Failure を完全網羅
final msg = switch (res) {
Success(:var value) => '計算結果: $value',
Failure(:var error) => 'エラー: $error',
};
print(msg);
}
}dart run result_pattern_demo.dart計算結果: 5 エラー: 0 で割ることはできません
この章では、Dartのエラーハンドリング手法について学びました。
try-catch / on / finally: 型安全な例外の捕捉、再スロー(rethrow)、クリーンアップ処理の実行。Exception を実装して独自のエラー型を定義。assert: 開発時・デバッグ時のみ有効な不変条件チェックでバグを早期発見。sealed class Result を使って成功と失敗を戻り値型で表現し、呼び出し側でのエラーハンドリング漏れを防止。次の 第12章 では、Dartチュートリアルの最終章として、並行・並列処理の仕組みである Isolate(アイソレート) について学びます。
パスワード強度チェック関数と、その呼び出し側のエラーハンドリングを実装してください。
class WeakPasswordException implements Exception を定義し、final String reason を保持する。void checkPassword(String password) 関数を定義する。
WeakPasswordException('パスワードは8文字以上必要です') をスローする。!password.contains(RegExp(r'[0-9]')))、WeakPasswordException('数字を1文字以上含める必要があります') をスローする。main() で 'abc' を渡して on WeakPasswordException で捕捉し、理由を出力する。// ここにクラスと関数を定義してください
void main() {
// ここで動作確認を行ってください
}dart run practice11_1.dartsealed クラスを用いた Result パターンを使い、安全なJSONパース関数を実装してください。
sealed class ParseResult<T> を定義し、ParseSuccess<T> と ParseFailure<T> を作成する。ParseResult<int> parsePositiveInt(String input) 関数を定義する。
int.tryParse(input) でパースを試み、失敗した場合は ParseFailure('数値を入力してください') を返す。ParseFailure('正の整数を入力してください') を返す。ParseSuccess(value) を返す。main() で '42', '-5', 'abc' を渡してテストし、switch 式で結果を出力する。// ここにクラスと関数を定義してください
void main() {
// ここで動作確認を行ってください
}dart run practice11_2.dart