📌

【Dart】Exception(例外)とError(間違い)の使い方

2020/12/18に公開

【Dart】Exception(例外)とError(間違い)

参考

事前知識

  • DartにはExceptionとErrorクラスがある

    • 馴染みのOSError等準備されたものがある(Implementers)
  • ExceptionとErrorの大まかな違い

    • Exception
      • プログラムの問題ではなく、実行中に異常が起こった場合のため、コード修正が不要
    • Error
      • プログラムの問題であり、コード修正が必要
  • 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を使いましょう
        (元の例外のスタックトレースを保持)
  • 説明の流れ

  1. throwやtry-catch(finallyやon含む)がどう利用できるかの簡単な確認
  2. Exceptionクラスの利用例
  3. Errorクラスの利用例
  4. rethrowを確認
  • 実行環境
    • DartPadやAndroid Studio等で実行
      • Dart SDK 2.10.4

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で処理可能
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で処理不能
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することは可能だが、重大な問題であるため推奨されない
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されない
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