📌
【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