発生してはならないエラーを処理する最良の方法は何ですか?
これを行う私の現在の方法は、次のように「発生してはならないこと」doesが発生した場合に例外をスローすることです。
/*
* Restoring from a saved state. This shouldn't be
* null unless someone in the future doesn't set it properly, in which
* case they will realize they did something wrong because it may crash.
*/
Object foo = bundle.getSerializable("foo");
if (foo != null) {
doSomethingWith(foo);
} else {
// This should never happen.
if (BuildConfig.DEBUG) {
throw new RuntimeException(
"Foo is null!");
}
foo = new Object();
doSomethingWith(foo);
}
これは絶対に発生しないはずです。しかし、コードが台無しになり、foo
がnullで本番環境にリリースされた場合に備えて、これらのタイプのチェックをコードに残すべきですか?
起きてはいけないことが頻繁に起こるのは驚くべきことです。
説明的なエラーメッセージでRuntimeExceptionをスローし、アプリケーションをクラッシュさせる必要があります。それが起こる場合は、何かが深く間違っていることを知っています。
インテリジェントに処理できない場合は、例外を処理しないことをお勧めします。
これにより、スタックの初期の何かが例外を処理する機会が得られます。これは、達成しようとしていることのコンテキストがおそらくあるためです。
または、これが発生してはならないことがわかっている場合、これはアサーションであり、おそらくassert
ステートメントを使用することでメリットが得られます。
Object foo = bundle.getSerializable("foo");
assert foo != null : "Foo was null"
doSomethingWith(foo);
私のコメントを繰り返すと、アプリケーションが不確定な状態にある場合(意図しないロジックのフローが原因)、破損や意図しない結果を防ぐために、できるだけ早くアプリケーションを強制終了するのが最善です。
コンピュータプログラミングは絶対的なものを扱います。 should、couldおよびmaybeのようなものはありません。エラーが発生するか、発生しません。発生する可能性が0%のエラー処理コードを追加すると、コードのにおいがします。適切なコードは、0.01%のオフの変化が発生した場合にそれを処理します。
よく考えるとこんな感じです。
考えられる問題の99%にのみエラー処理を追加する場合、ソースコードに未処理の問題が発生するために必要なのは100の問題だけです。
残業ソースコードは数千の問題に対処するために成長します...
私がいつも目にする問題の1つは、エラー処理で爆破されたソースコードです。特にJavaの場合、例外処理はコンパイラエラーによって強制されます。
さまざまなエラーのケースをすべて処理するために何回も分岐する場合、関数の目的を読み取ることが難しくなります。
代わりに、エラー処理を特殊な関数に分けてください。元の機能を単一の目的に焦点を合わせたままにします。
例えば;
public Object getSerializableSafely(Bundle bundle, String name)
{
Object foo = bundle.getSerializable(name);
if (foo != null)
{
return foo;
}
else if (BuildConfig.DEBUG) {
throw new RuntimeException("Foo is null!");
}
return null;
}
public boolean Worker()
{
Object foo = getSerializableSafely(bundle, "foo");
if (foo == null)
{
return false;
}
// do work with foo
return true;
}
デバッグビルドでランタイム例外をスローしても問題はありません。彼らはデバッガーを問題のある場所に直接連れて行くので、彼らは素晴らしい時間節約になります。
問題は、Worker
のような関数を作成し、失敗しないことを期待することです。操作の結果としてtrue/falseを報告することは、すべてをtry/catchでラップするよりもはるかに優れています。
XML解析APIを使用していた場合。無効なXMLをフィードした場合に例外をスローしますか、それとも解析関数がfalseを返しますか?例外的なケースに関連する例外処理を保持し、戻り結果を使用して関数の結果を伝えます。
文字列をUTF-8バイトに変更したり、UTF-8バイトから変更したりするときに、常につまずくのは「サポートされていないエンコーディング例外」です。 UTF-8はいつサポートされなくなりますか?
ただし、注意してください。「発生してはならない」には2つのレベルがあります。 UTF-8がサポートされていないレベルがあります。次に、開発者がテスターの肩越しに立ち、点滅してつぶやく「決して起こらないはずの」状態で立ち上がると、「決して起こらないはず」があります。
私が最初のレベルで通常行うことは、そのロジックを、例で行ったことを実行する別のクラスにロールアップすることです。それをキャッチして、RuntimeExceptionに変換します。そうすれば、厄介で無関係なtry ... catch
ブロックは隠されており、残りのコードとは別にテストされています。
2番目のケースの場合、開発者の「すべき決して起こらない」という言葉は、「例外」という言葉の適切な意味で、実際には例外です。しかし、それらをガード条件として扱う方がきれいです。そのケースが発生した場合、すべての賭けは無効になり、コードはそのユースケースを完了できないためです。
Object foo = bundle.getSerializable("foo");
// Guard: This should never happen.
if (foo == null) {
throw new RuntimeException("Foo is null!");
}
foo.doSomething();
ルールは「すぐに失敗する」ので、仮定が間違っていることを検出した時点で、コードを停止する必要があります。値を逆参照しようとするとすぐにコードが停止するため、ヌルポインターの例は「フェイルファースト」の例ですが、特定するのが難しいエラーの1つです。
したがって、この場合、空のfooをインスタンス化して運転することは、「fail fast」の違反です。そのため、それを行わないでください。
また、クライアント/サーバーコントラクトに依存するのも公平です。そのため、行うすべての呼び出しの後にnullチェックを行う必要はありません。内部呼び出しを行っている(そして外部からのデータを検証していない)限り、クライアント/サーバーコントラクトのすべての仮定を検証する必要はありません。関数がnull以外の値を返すと述べている場合は、それを信頼してください。失敗した場合は、クライアントではなく関数を修正してください。
特にnullポインター例外の場合、Guavaには、型をラップし、値がnullでないか存在しないことを確認する、本当に素晴らしいOptionalクラスがあります。
例外のスローはコストがかかり、OSとプラットフォームに基づいてユーザーにショックを与える可能性があります。さらに、チェックが簡単なブロッキングエラー、つまりnull値があります。例外をスローする代わりに、ユーザーにメッセージを表示し、それらのアクションを必要とするリソースのClosing()およびDisposing()を表示して、正常に終了する必要があります。