【Dart】ExceptionとErrorを分けて考える
Qiitaの記事と同じ内容のものになります。
Exception
とError
の扱い
DartにおけるDartではエラーを大別するとException
とError
の2種類に分けられるのですが、エラー発生時の扱いが異なります。
Exception
は catch されることを想定しており、プログラムで対応できるエラーを扱います。
逆にError
は catch されることを想定しておらず、プログラムの実装が間違っているため発生するエラーを扱います。
実装者が避けるべきものであり、もしError
が発生した場合catchするのではなくプログラムを終了させるようドキュメントに書かれています。
catch されることを意図していないエラーについてはError
やそのサブクラスを用いるようException
のドキュメントにも書かれています。
Exception
(原文)
A marker interface implemented by all core library exceptions.An Exception is intended to convey information to the user about a failure, so that the error can be addressed programmatically. It is intended to be caught, and it should contain useful data fields.
Creating instances of Exception directly with Exception("message") is discouraged in library code since it doesn't give users a precise type they can catch. It may be reasonable to use instances of this class in tests or during development.
For failures that are not intended to be caught, use Error and its subclasses.
(ChatGPT 和訳)
全てのコアライブラリ例外によって実装されるマーカーインターフェース。Exception(例外)は、エラーが発生した際に、そのエラーをプログラムで対応できるようにユーザーに情報を伝えることを目的としています。この例外は捕捉されることを意図しており、有用なデータフィールドを含むべきです。
ライブラリコード内で直接 Exception("message") のように Exception のインスタンスを作成することは推奨されていません。なぜなら、ユーザーが捕捉できる正確な型を提供しないからです。しかし、テストや開発中にこのクラスのインスタンスを使用することは合理的です。
捕捉されることを意図していない失敗については、Error やそのサブクラスを使用してください。
Error
(原文)
Error objects thrown in the case of a program failure.An Error object represents a program failure that the programmer should have avoided.
Examples include calling a function with invalid arguments, or even with the wrong number of arguments, or calling it at a time when it is not allowed.
These are not errors that a caller should expect or catch — if they occur, the program is erroneous, and terminating the program may be the safest response.
When deciding that a function should throw an error, the conditions where it happens should be clearly described, and they should be detectable and predictable, so the programmer using the function can avoid triggering the error.
(ChatGPT 和訳)
プログラムの失敗時にスローされるErrorオブジェクト。Errorオブジェクトは、プログラマーが回避すべきプログラムの失敗を表します。
例えば、無効な引数を使って関数を呼び出した場合や、誤った引数の数で関数を呼び出した場合、または許可されていないタイミングで呼び出した場合などが挙げられます。
これらは呼び出し側が期待したり捕捉したりすべきエラーではありません。もしこれらが発生した場合、プログラムは誤っており、プログラムを終了させることが最も安全な対応になる可能性があります。
関数がエラーをスローするように設計する際には、その条件を明確に記述し、エラーが発生する状況が検出可能で予測可能であるべきです。これにより、関数を使用するプログラマーがそのエラーを引き起こさないように回避できるようになります。
エラーハンドリング
このことを基に、エラーハンドリングのベストプラクティスは Effective Dart の Error Handling の項に記載されています。
on
clauses
AVOID catches without on
句を用いて catch するException
やError
の種別をフィルタリングするべき。
on
句を用いないとあらゆる例外(Exception
もError
も)を catch してしまいArgumentError
なども握りつぶしてしまう。(→Pokémon exception handling)
もし実行時エラーをハンドリングしたい場合は、基底クラスであるException
を catch すると良い
try {
...
} on FormatException catch (e) {
// FormatExceptionのハンドリング
} on Exception (e) {
// その他例外のハンドリング
}
原文
A catch clause with no on qualifier catches anything thrown by the code in the try block. Pokémon exception handling is very likely not what you want. Does your code correctly handle StackOverflowError or OutOfMemoryError? If you incorrectly pass the wrong argument to a method in that try block do you want to have your debugger point you to the mistake or would you rather that helpful ArgumentError get swallowed? Do you want any
assert()
statements inside that code to effectively vanish since you're catching the thrown AssertionErrors?The answer is probably "no", in which case you should filter the types you catch. In most cases, you should have an
on
clause that limits you to the kinds of runtime failures you are aware of and are correctly handling.In rare cases, you may wish to catch any runtime error. This is usually in framework or low-level code that tries to insulate arbitrary application code from causing problems. Even here, it is usually better to catch Exception than to catch all types. Exception is the base class for all runtime errors and excludes errors that indicate programmatic bugs in the code.
on
clauses
DON'T discard errors from catches without もしon
句を用いず全てのエラーを catch する場合は、ログへの記録、ユーザーへの表示、必要に応じて再スローするなどして握り潰さないようにする。
try {
...
} catch (e) {
// ログとして保存
print(e);
// 必要に応じて再スロー
rethrow;
}
原文
If you really do feel you need to catch everything that can be thrown from a region of code, do something with what you catch. Log it, display it to the user or rethrow it, but do not silently discard it.
Error
only for programmatic errors
DO throw objects that implement Error
もしくはそのサブクラスがスローされた場合、それはコードにバグが存在することを意味する。
また、APIが誤って使用されているなどコード修正が必要な場合にError
をスローする。
それ以外の実行時エラーの場合は Error
をスローするのではなく、Exception
や他の型をスローするようにする。
原文
The Error class is the base class for programmatic errors. When an object of that type or one of its subinterfaces like ArgumentError is thrown, it means there is a bug in your code. When your API wants to report to a caller that it is being used incorrectly throwing an Error sends that signal clearly.
Conversely, if the exception is some kind of runtime failure that doesn't indicate a bug in the code, then throwing an Error is misleading. Instead, throw one of the core Exception classes or some other type.
Error
or types that implement it
DON'T explicitly catch Error
を catch してしまうとバグが隠れてしまう。
発生したError
のハンドリング処理を追加するのではなく、プログラムを停止させ原因となるコードの修正をするべき。
原文
This follows from the above. Since an Error indicates a bug in your code, it should unwind the entire callstack, halt the program, and print a stack trace so you can locate and fix the bug.
Catching errors of these types breaks that process and masks the bug. Instead of adding error-handling code to deal with this exception after the fact, go back and fix the code that is causing it to be thrown in the first place.
rethrow
to rethrow a caught exception
DO use Exception
を再スローする場合は、例外オブジェクトをthrow
で投げ直すのではなく、rethrow
を使用する。
throw
で投げ直すとスタックトレースが最後にスローされた位置にリセットされてしまうが、rethrow
なら元のスタックトレースが保持される。
try {
somethingRiskey();
} catch (e) {
if (!canHandle(e)) rethrow;
handle(e);
}
原文
If you decide to
rethrow
an exception, prefer using the rethrow statement instead of throwing the same exception object usingthrow
.rethrow
preserves the original stack trace of the exception.throw
on the other hand resets the stack trace to the last thrown position.
まとめ
DartではError
とそのサブクラスは、「実装ミスで、コードの修正が必要」であるため catch しない方が良いということがわかりました。
on
句を用いないとException
だけでなくError
も一緒に catch してハンドリングされてしまうため、Exception
の種別ごとにon
句でフィルタリングをし、それ以外の実行時エラーをハンドリングする場合はException
を指定するようにして、Error
がハンドリングされないよう注意する必要があります。
また独自のエラークラスを実装する場合は、スローされる独自実装のエラーが「コードのバグで修正が必要なものなのか」という点を考えて、Exception
を継承するのかError
を継承するのか考えるべきだなと思います。
それに伴う命名も注意が必要だと思います。
ハンドリングされる想定のエラーなのに「**Error
」という名前は不適切だと思いますし、反対に「**Exception
」というエラーがスローされてアプリがクラッシュしてると実装ミスなのか、命名が間違ってるだけなのかの判断がつかないです。
参考サイト
Discussion