異常エラー処理

Dartの例外はすべて非検査例外です。関数にthrowする型を宣言する必要はなく、捕捉も強制されません。

大きく2種類に分かれます。

  • Exception:業務ロジック上予測可能なエラー(フォーマット違反、引数不正など。捕捉して対応するのが推奨)
  • Error:プログラム致命的なエラー(型不一致、ヌルポインタ、未実装メソッドなど。基本的に捕捉しない)

null以外のオブジェクトは何でもthrowで投げられますが、実務プロジェクトではException/Errorのサブクラスだけを投げるルールにするのが一般的です。

捕捉されなかった例外は現在のアイソレート(isolate)を強制終了させ、アプリがクラッシュします。

throw で例外を投げる

1. 標準搭載の例外を投げる

void main() {
  // フォーマット例外
  throw FormatException("データは1セグメント以上必要です");
}
Code language: Dart (dart)
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
FormatException: エラー説明文
#0      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:3:3)
#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#2      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

上記コードは自分でメッセージを設定した標準例外を明示的に投げ、実行時にエラーを出力します。一見使い道が分かりにくいかもしれませんが、例外を投げる仕組みは開発上欠かせません。例えばユーザー登録処理で入力値のルールチェックを行い、不適切な値が来た時点で例外を投げて処理を中断、ユーザーに不備を通知することで、不正なデータがシステムに混入するのを防ぎ、データの整合性を保てます。

2. 任意のオブジェクトを投げる

本番環境での使用は推奨されません

void main() {
  throw "エラーテキスト!";
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
エラーテキスト!
#0      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:2:3)
#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#2      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

3. アロー関数内でthrowを使用(throwは式として扱える)

class Point {}
void distanceTo(Point other) => throw UnimplementedError("このメソッドは未実装です");

void main() {
  distanceTo(Point());
}
Code language: JavaScript (javascript)

三、try /on/catch で例外を捕捉する

構文の違い

  • on 例外型:指定した型の例外のみマッチ、例外インスタンスを取得できない
  • catch(e):すべての例外を捕捉、変数eに例外インスタンスが入る
  • catch(e, s):e = 例外オブジェクト、s = 実行スタック情報 StackTrace

1:onで特定の単一例外を捕捉


// Exceptionを実装した独自例外クラスを定義
class OutOfLlamasException implements Exception {}

// 上記の独自例外を投げる関数
void breedMoreLlamas() {
  throw OutOfLlamasException();
}

void buyMoreLlamas() {
  print("buyMoreLlamasが実行されました");
}

void main() {
  try {
    breedMoreLlamas(); // 処理を実行
  } on OutOfLlamasException {
    buyMoreLlamas(); // OutOfLlamasExceptionを捕捉したらこちらを実行
  }
}Code language: Dart (dart)

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
buyMoreLlamasが実行されましたCode language: CSS (css)
try  {
    // エラーが発生する可能性のあるコード
} on 例外型 {
   // エラーを捕捉した際に実行する処理
}Code language: Dart (dart)

try onは型を指定して例外を受け取るための専用構文です。

2:複数分岐による段階的な捕捉

より詳細なマッチングが優先されます

class OutOfLlamasException implements Exception {}

void breedMoreLlamas() {
  throw FormatException("データフォーマットが不正です");
}

void main() {
  try {
    breedMoreLlamas();
  }
  // 1. 独自例外を完全一致で捕捉
  on OutOfLlamasException {
    print("OutOfLlamasExceptionが発生しました");
  }
  // 2. Exceptionのサブクラスを一括捕捉
  on Exception catch (e) {
    print("一般的な業務例外:$e");
  }
  // 3. Errorやその他投げられたオブジェクトを受けるフォールバック
  catch (e) {
    print("未知の重大エラー:$e");
  }
}
Code language: Dart (dart)

出力結果 : 一般的な業務例外:FormatException: データフォーマットが不正です

複数のonを並べることで個別のExceptionを分けて処理でき、最後にcatchで全てのケースを補完できます。

3:例外を捕捉し、実行スタックを出力

void main() {
  try {
    int a = null;
    print(a + 1);
  } catch (e, stack) {
    print("例外メッセージ:$e");
    print("完全な実行スタック:\n$stack");
  }
}
Code language: Dart (dart)

このコードはエラーメッセージだけでなく、関数の呼び出し経路となるスタック情報も出力してデバッグに役立てられます。

D:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:3:13: Error: A value of type 'Null' can't be assigned to a variable of type 'int'.
    int a = null;Code language: PHP (php)

四、rethrow

一部処理を実行した後、上位に例外を再送出

役割:現在の関数内で一部復旧処理を行った上で、呼び出し元に例外を渡して追加処理を実施させる

場合によっては現在の関数内だけで完結せず、呼び出し元にもエラーを通知して別途対応してもらいたいケースがあります。

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // trueにインクリメントは実行できず、実行時エラーとなる
  } catch (e) {
    print("misbehave内のローカル処理:${e.runtimeType}");
    rethrow; // 再度投げ、外側のtryで捕捉させる
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print("main側で最終処理:${e.runtimeType}");
  }
}
Code language: Dart (dart)

実行フロー:

misbehave内のローカル処理:NoSuchMethodError
main側で最終処理:NoSuchMethodError

上記コードではmisbehave関数とmain関数の両方で例外に対する処理が走ります。

五、finally 最終ブロック

エラーの有無に関わらず必ず実行される

代表的な2パターン:

  1. try + finallyのみ:例外を捕捉しないため、finally実行後にプログラムがクラッシュする
  2. try + catch + finally:まず例外処理を行い、最後にfinallyを実行(リソース解放用)

1:try + finally

void cleanLlamaStalls() => print("リソースをクリーンアップ、必ず実行される処理");
void breedMoreLlamas() => throw Exception("エラーが発生しました");

void main() {
  try {
    breedMoreLlamas();
  } finally {
    cleanLlamaStalls();
  }
}
Code language: Dart (dart)

クリーンアップ処理は実行されるものの、捕捉されていない例外によりプログラムはクラッシュします。

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
リソースをクリーンアップ、必ず実行される処理
Unhandled exception:
Exception: エラーが発生しました
#0      breedMoreLlamas (file:///D:/dartdemo/firstdart/bin/firstdart.dart:2:27)
#1      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:5)
#2      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#3      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

2:try + catch + finally

ファイルオープン後のクローズなど、リソース解放の定番記述法

void cleanLlamaStalls() => print("ファイルを閉じる/リソースを解放");
void breedMoreLlamas() => throw FormatException("パースに失敗しました");

void main() {
  try {
    breedMoreLlamas();
  } catch (e) {
    print("例外を捕捉:$e");
  } finally {
    cleanLlamaStalls();
  }
}
Code language: Dart (dart)

実行順序:例外発生 → catchで処理 → finally実行

実行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
例外を捕捉:FormatException: パースに失敗しました
ファイルを閉じる/リソースを解放

六、assert アサーション

開発・デバッグ時のみ有効、本番環境では完全に無効化される

assert(論理式, [任意のエラーメッセージ])

条件がfalseの場合AssertionErrorを投げます。本番ビルドでは完全に無視され、実行コストも発生しません。

1:基本的なアサーション

void main() {
  String? text;
  int number = 120;
  String url = "http://foxdevelop.com";

  assert(text != null); // nullでないか検証
  assert(number < 100); // 数値範囲チェック
  assert(url.startsWith("https")); // URLプロトコル検証
}
Code language: JavaScript (javascript)

このコードをそのまま実行しても何も出力されません!

既定でアサーションは無効になっているため、専用の起動オプションで有効化する必要があります。下記を参照してください。

2:独自のエラーメッセージを設定

void main() {
  String url = "http://test.com";
  assert(
    url.startsWith("https"),
    "リンク $url はhttpsで始める必要があります",
  );
}
Code language: JavaScript (javascript)

アサーション失敗時に表示するメッセージを自由に定義できます。

アサーションの有効化方法

  1. Flutter Debugモード:標準でアサーションが有効
  2. dartコマンド実行時:dart --enable-asserts ファイル名.dart
  3. 本番パッケージ:アサーションのコードが全て削除され、パフォーマンス低下なし
D:\dartdemo\firstdart\bin>dart --enable-asserts firstdart.dart
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

dart runと単なるdartコマンドは同じく既定でアサーションが無効のため、--enable-asserts引数を追加する必要があります。

実行コマンド例

dart run --enable-asserts main.dartCode language: CSS (css)

ターミナルがプロジェクトルートにいる場合は、簡略化して以下を実行可能です。

実行コマンド:

dart run --enable-asserts

dart run:Dartソースを直接実行、開発デバッグ向け

dart ファイル名:単一ファイルを簡易実行

dart compile exe:実行ファイルにコンパイル。出力されたバイナリでは常にassertが無効

Dartコードは配下のbinフォルダに配置されているため、bin内に移動してファイルを直接実行するか、一段上のプロジェクトルートからプロジェクト単位で実行できます。

dart run –enable-asserts

下記にbin配下から実行するパターン、プロジェクトルートから実行するパターンの両方を記載します。

D:\dartdemo\firstdart\bin>dart --enable-asserts firstdart.dart
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)

D:\dartdemo\firstdart\bin>dart run
Building package executable...
Built firstdart:firstdart.

D:\dartdemo\firstdart\bin>cd ..

D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

アサーションの条件がtrueであれば、例外は発生しません。

上記コードを修正:urlのhttpをhttpsに変更、text != nullをtext == null、numberの値を99に変更

void main() {
  String? text;
  int number = 99;
  String url = "https://foxdevelop.com";

  assert(text == null); // nullチェック
  assert(number < 100); // 数値範囲検証
  assert(url.startsWith("https")); // URLプロトコル検証
}Code language: Dart (dart)

再度実行するとエラーは出力されません。

D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.Code language: CSS (css)

七、サンプルコード

void cleanResource() => print("finally:リソースを解放");

void testError() {
  try {
    int num = 150;
    assert(num < 100, "数値は100未満にする必要があります");
    throw FormatException("データフォーマットが不正");
  } on FormatException catch (e) {
    print("フォーマット例外を捕捉:$e");
    rethrow;
  } finally {
    cleanResource();
  }
}

void main() {
  try {
    testError();
  } catch (e) {
    print("main側で再送出された例外を捕捉:$e");
  }
}
Code language: PHP (php)

実行結果

D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.
finally:リソースを解放
main側で再送出された例外を捕捉:'file:///D:/dartdemo/firstdart/bin/firstdart.dart':
 Failed assertion: line 6 pos 12: 'num < 100': 数値は100未満にする必要がありますCode language: PHP (php)

例外処理全体の流れをまとめた実例です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です