📌
【Dart】Exception(例外)とError(間違い)の使い方
【Dart】Exception(例外)とError(間違い)
参考
- 公式サイト
事前知識
- 
DartにはExceptionとErrorクラスがある - 馴染みのOSError等準備されたものがある(Implementers)
 
- 
ExceptionとErrorの大まかな違い - Exception
- プログラムの問題ではなく、実行中に異常が起こった場合のため、コード修正が不要
 
- Error
- プログラムの問題であり、コード修正が必要
 
 
- Exception
- 
Error handlingについて - AVOID catches without on clauses.
- on句無しでのcatchはerrorもcatchしてしまうので避けましょう
 
- DON’T discard errors from catches without on clauses.
- on句無しでのcatchを利用した場合、errorを必ず出力しましょう
 
- DO throw objects that implement Error only for programmatic errors.
- プログラムの問題であり、修正が必要な場合のみErrorオブジェクトをthrowしましょう
 
- DON’T explicitly catch Error or types that implement it.
- errorをcatchすると、本当のエラー原因(バグ)が分かりづらくなるので避けましょう
 
- DO use rethrow to rethrow a caught exception.
- catchした後に、再度例外を発生させて止める場合は、rethrowを使いましょう
 (元の例外のスタックトレースを保持)
 
- catchした後に、再度例外を発生させて止める場合は、rethrowを使いましょう
 
- AVOID catches without on clauses.
- 
説明の流れ 
- throwやtry-catch(finallyやon含む)がどう利用できるかの簡単な確認
- Exceptionクラスの利用例
- Errorクラスの利用例
- rethrowを確認
- 実行環境
- 
DartPadやAndroid Studio等で実行
- Dart SDK 2.10.4
 
 
- 
DartPadやAndroid Studio等で実行
throwを確認
- Exceptionをthrow
void main() {
  print('One');
  throw Exception();
  print('Two');
}
実行結果
One
Uncaught Error: Exception
- Errorをthrow
void main() {
  print('One');
  throw Error;
  print('Two');
}
実行結果
One
Uncaught Error: Error
- 【備考】作成したクラスをthrow
class TestClass {}
void main() {
  print('One');
  throw TestClass();
  print('Two');
}
実行結果
One
Uncaught Error: Instance of 'TestClass'
- 【備考】文字列クラスをthrow
void main() {
  print('One');
  throw 'TEST';
  print('Two');
}
実行結果
One
Uncaught Error: TEST
- 【備考】nullをthrow
void main() {
  print('One');
  throw null;
  print('Two');
}
実行結果
One
Uncaught Error: Throw of null.
try-catch文を確認
- Exceptionをcatch
void main() {
  try {
    print('Try One');
    throw Exception();
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  }
}
実行結果
Try One
Catch
Exception
- Errorをcatch
void main() {
  try {
    print('Try One');
    throw Error;
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  }
}
実行結果
Try One
Catch
Error
- Exceptionをcatch+catch内でスタックトレース出力+finally追加
- ※Errorも同様
 
void main() {
  try {
    print('Try One');
    throw Exception();
    print('Try Two');
  } catch (e, st) {
    print('Catch');
    print(e);
    print(st);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
Exception
Exception
    at Object.wrapException (<anonymous>:335:17)
    at Object.main (<anonymous>:2428:17)
    at <anonymous>:2934:9
    at <anonymous>:2918:7
    at dartProgram (<anonymous>:2929:5)
    at <anonymous>:2936:3
    at replaceJavaScript (https://dartpad.dev/scripts/frame_dark.html:38:27)
    at messageHandler (https://dartpad.dev/scripts/frame_dark.html:54:17)
Finally
After Finally
- throwではないRangeErrorをcatch
void main() {
  try {
    print('Try One');
    List<String> listStrings = List(10);
    listStrings[10];
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
RangeError (index): Index out of range: index should be less than 10: 10
Finally
After Finally
- FormatExceptionをcatch
- FormatExceptionクラスはExceptionクラスをImplementしている
 
void main() {
  try {
    print('Try One');
    throw FormatException("throw exception test");
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
FormatException: throw exception test
Finally
After Finally
- on句を利用し、FormatException発生時に処理実施
void main() {
  try {
    print('Try One');
    throw FormatException();
    print('Try Two');
  } on FormatException {
    print('On');
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
On
Finally
After Finally
- on句を利用し、Exception発生時に処理実施
void main() {
  try {
    print('Try One');
    throw Exception();
    print('Try Two');
  } on Exception {
    print('On');
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
On
Finally
After Finally
- on句を利用し、FormatException発生時に処理実施
- Exception > FormatExceptionの関係なので、on Exceptionで処理可能
 
- Exception > FormatExceptionの関係なので、
void main() {
  try {
    print('Try One');
    throw FormatException();
    print('Try Two');
  } on Exception {
    print('On');
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
On
Finally
After Finally
- on句を利用し、Exception発生時に処理実施
- Exception > FormatExceptionの関係なので、on FormatExceptionで処理不能
 
- Exception > FormatExceptionの関係なので、
void main() {
  try {
    print('Try One');
    throw Exception();
    print('Try Two');
  } on FormatException {
    print('On');
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Finally
Uncaught Error: Exception
- on句では、複数のターゲットを指定可能
- FormatException発生時に処理実施
 
void main() {
  try {
    print('Try One');
    throw FormatException();
    print('Try Two');
  } on FormatException {
    print('On:FormatException');
  } on Exception {
    print('On:Exception');
  } on Error {
    print('On:Error');
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
On:FormatException
Finally
After Finally
- on句では、複数のターゲットを指定可能
- FormatException発生時に処理実施
- 複数指定指定した際は、上から記載順に当てはまれば処理実施
 
void main() {
  try {
    print('Try One');
    throw FormatException();
    print('Try Two');
  } on Exception {
    print('On:Exception');
  } on FormatException {
    print('On:FormatException');
  } on Error {
    print('On:Error');
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
On:Exception
Finally
After Finally
- on句では、catchと併用可能
- ※FormatException等も同様
 
void main() {
  try {
    print('Try One');
    throw Exception();
    print('Try Two');
  } on Exception catch (e) {
    print('On + Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
On + Catch
Exception
Finally
After Finally
- on句でターゲットとしたものではないExceptionが発生した場合も、最後にcatch (e)を置くと処理可能- ※ダメな例(Errorでも何でもcatch可能なため)
 
void main() {
  try {
    print('Try One');
    throw IntegerDivisionByZeroException();
    print('Try Two');
  } on FormatException {
    print('On:FormatException');
  } catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
IntegerDivisionByZeroException
Finally
After Finally
try-catch文の注意点
- 
公式でも実際の利用では、Errorを全てCatchするような書き方はよくないとされている 
- 
以下はよくない 
void main() {
  try {
    print('Try One');
    throw Error;
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
- 以下の方がよい
- ErrorはErrorとして発生させる
 
void main() {
  try {
    print('Try One');
    throw Error;
    print('Try Two');
  } on Exception catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Finally
Uncaught Error: Error
Exceptionクラス利用例
- Exceptionクラスをimplements(インターフェイスを利用)し、例外クラスを作成
- 
toString()をoverrideしない場合は、print(e)の出力は以下となる- Instance of 'TestException'
 
- 備考
- 本来は、TestExceptionではなく、分かりやすい名前を付けることが大切
- 公式のクラス説明では、 Exception("message")の形はテストや開発では便利かもしれないが、ライブラリコードとしては推奨されない旨の記載あり
 
- 本来は、
 
- 
class TestException implements Exception {
  final String message;
  const TestException(this.message);
  
  String toString() => message;
}
void main() {
  try {
    print('Try One');
    throw TestException('Test Exception');
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
Instance of 'TestException'
Finally
After Finally
Errorクラス利用例
- Errorクラスをextends(継承)し、Errorクラスを作成
- テストとしてcatchする場合
- 本来Errorはプログラム修正すべきもののため、catchしない
- ※実際にErrorをcatchすることは可能だが、重大な問題であるため推奨されない
 
 
- 本来Errorはプログラム修正すべきもののため、catchしない
 
- テストとしてcatchする場合
class TestError extends Error {
  final String message;
  TestError(this.message);
  
  String toString() => message;
}
void main() {
  try {
    print('Try One');
    throw TestError('Test Error');
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
Test Error
Finally
After Finally
- assert関数を利用=条件時にAssertionErrorをthrow
- 本来catchする必要はない
- ※DartPadではエラーはthrowされない
 
 
- 本来catchする必要はない
class User {
  final int id;
  User({this.id}) : assert(id != 0);
}
void main() {
  try {
    print('Try One');
    final user = User(id: 0);
    print(user.id);
    print('Try Two');
  } catch (e) {
    print('Catch');
    print(e);
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
'package:xxx/main.dart': Failed assertion: line 3 pos 28: 'id != 0': is not true.
Finally
※catchしない場合に実行すると以下のようなエラーとなる
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: 'package:xxx/main.dart': Failed assertion: line 3 pos 28: 'id != 0': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:42:39)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:38:5)
#2      new User (package:xxx/main.dart:3:28)
#3      main (package:xxx/main.dart:7:16)
#4      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:241:25)
#5      _rootRun (dart:async/zone.dart:1184:13)
#6      _CustomZone.run (dart:async/zone.dart:1077:19)
#7      _runZoned (dart:async/zone.dart:1619:10)
#8      runZonedGuarded (dart:async/zone.dart:1608:12)
#9      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:233:5)
#10     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#11     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
rethrowを確認
- catchした後に、再度例外を発生させて止める場合は、rethrowを利用
 (元の例外のスタックトレースを保持)- ※DartPadではエラー発生時にスタックトレースは出力されない
 
class TestException implements Exception {
  final String message;
  const TestException(this.message);
  
  String toString() => message;
}
void main() {
  try {
    print('Try One');
    throw TestException('Test Exception');
    print('Try Two');
  } on Exception catch (e,st) {
    print('Catch');
    print(e);
    print(st);
    rethrow;
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果(※DartPadの出力結果)
Try One
Catch
Test Exception
Test Exception
    at Object.wrapException (<anonymous>:335:17)
    at Object.main (<anonymous>:2425:17)
    at <anonymous>:2942:9
    at <anonymous>:2926:7
    at dartProgram (<anonymous>:2937:5)
    at <anonymous>:2944:3
    at replaceJavaScript (https://dartpad.dev/scripts/frame_dark.html:38:27)
    at messageHandler (https://dartpad.dev/scripts/frame_dark.html:54:17)
Finally
Uncaught Error: Test Exception
実行結果(※IDEの出力結果)
Try One
Catch
Test Exception
#0      main (package:xxx/main.dart:12:5)
#1      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:241:25)
#2      _rootRun (dart:async/zone.dart:1184:13)
#3      _CustomZone.run (dart:async/zone.dart:1077:19)
#4      _runZoned (dart:async/zone.dart:1619:10)
#5      runZonedGuarded (dart:async/zone.dart:1608:12)
#6      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:233:5)
#7      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#8      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
Finally
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Test Exception
#0      main (package:xxx/main.dart:12:5)
#1      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:241:25)
#2      _rootRun (dart:async/zone.dart:1184:13)
#3      _CustomZone.run (dart:async/zone.dart:1077:19)
#4      _runZoned (dart:async/zone.dart:1619:10)
#5      runZonedGuarded (dart:async/zone.dart:1608:12)
#6      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:233:5)
#7      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#8      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
- 【備考】rethrowではなく、throwを再度利用するとスタックトレースの内容が変わってしまう
- main (package:xxx/main.dart:12:5)
- main (package:xxx/main.dart:18:5)
 
class TestException implements Exception {
  final String message;
  const TestException(this.message);
  
  String toString() => message;
}
void main() {
  try {
    print('Try One');
    throw TestException('Test Exception');
    print('Try Two');
  } on Exception catch (e, st) {
    print('Catch');
    print(e);
    print(st);
    throw TestException('Test Exception');
  } finally {
    print('Finally');
  }
  print('After Finally');
}
実行結果
Try One
Catch
Test Exception
#0      main (package:xxx/main.dart:12:5)
#1      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:241:25)
#2      _rootRun (dart:async/zone.dart:1184:13)
#3      _CustomZone.run (dart:async/zone.dart:1077:19)
#4      _runZoned (dart:async/zone.dart:1619:10)
#5      runZonedGuarded (dart:async/zone.dart:1608:12)
#6      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:233:5)
#7      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#8      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
Finally
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Test Exception
#0      main (package:xxx/main.dart:18:5)
#1      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:241:25)
#2      _rootRun (dart:async/zone.dart:1184:13)
#3      _CustomZone.run (dart:async/zone.dart:1077:19)
#4      _runZoned (dart:async/zone.dart:1619:10)
#5      runZonedGuarded (dart:async/zone.dart:1608:12)
#6      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:233:5)
#7      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#8      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)


Discussion