Joshua Blochの「Effective Java-Second Edition」」を見て、152ページにある次のコードを見つけました。
double apply(double x, double y) {
switch(this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
}
throw new AssertionError("Unknown op: " + this);
}
ここで私を混乱させるのは、AssertionError
が積極的にスローされていることです。それは良い習慣と考えられていますか?私の理解では、アサーションはコードと干渉しないように使用されているため、Javaプログラミングがアサーションを有効にせずに開始され、assert-statementsが実行されない場合、動作は変わりません。アサーションを有効にせずにプログラムを実行したときにAssertionException
が表示されると、かなり混乱します。
例のケースが頻繁に発生する可能性があることは理解していますが、いくつかの異なるオプションを分析し、それがどれでもない場合は、例外をスローする必要があります。
ここでAssertionException
をスローするのは良い習慣ですか、それとも別のものをスローする方が良いでしょうか?もしそうなら、どれが一番合うでしょうか?多分IllegalArgumentException
?
明確にするために編集:私の質問は、Error
hereをスローするかどうかではなく、Exception
またはError
をスローする場合、どちらを選択する必要がありますか?そして、AssertionError
sを積極的にスローすることは良い習慣ですか?ドキュメントにはアサーションが失敗したことを示すためにスローされますとあるので、積極的にスローするべきではないと感じています。あれは正しいですか?
2番目の編集:明確な質問:AssertionError
を積極的にスローすることは良い習慣ですか、それが可能であっても、それを避けるべきですか? (ドキュメントを読んでいる私の推測は後者です)
私はここでブロッホ氏に同意します-代替案(IllegalArgumentException
、IllegalStateException
、およびUnsupportedOperationException
)は問題の重大度を適切に伝えておらず、発信者は誤ってこのケースをキャッチして処理します。実際、この行に到達した場合、問題のプログラムはbrokenであり、唯一の正気なことは終了することです。
ここでのポイントは、列挙型には有限の値のセットがあるため、throw
行に到達することは不可能であるべきです-このインスタンスメソッドを修正せずに列挙型の定義が変更された場合にのみ発生します。 RuntimeException
をスローすると、実際にはメソッド(および列挙型)自体が壊れているときに、呼び出し元がミスをしたことを示しています。 AssertionError
を明示的に発生させることは、このメソッドが予期する不変式が違反されていることを示します。
グアバには、ブレークダウンについての役立つ記事があります さまざまなタイプの例外を発生させる場合 。彼らは書きます:
従来のアサーションは、クラス自体(チェックを含む)が何らかの方法で壊れている場合にのみ失敗するチェックです。 (場合によっては、これがパッケージにまで及ぶ可能性があります。)これらは、後置条件、クラス不変式、および内部前提条件(非パブリックメソッド)を含むさまざまな形式をとることができます。
不可能な条件のチェックは、周囲のコードが後で変更されない限り、またはプラットフォームの動作に関する最も深い前提に著しく違反しない限り、失敗する可能性がないものです。これらは不必要なはずですが、ステートメントが到達不可能であることをコンパイラーが認識できないため、またはコンパイラーが推測できない制御フローについて何かを知っているため、しばしば強制されます。
このページでは、AssertionError
がこれらのケースを処理するための推奨される方法であると述べています。 Verify
クラスのコメントも、例外の選択に関するいくつかの有用な洞察を提供します。 AssertionError
が強すぎると思われる場合、VerifyException
を上げることは良い妥協案です。
Error
またはRuntimeException
の特定の質問については、それは本当に問題ではありません(どちらもオフになっているため、キャッチされずに呼び出しスタックを上に移動する可能性があります)が、呼び出し元はRuntimeException
からの回復を試みます。このような場合のアプリケーションのクラッシュは機能です。それ以外の場合は、(現時点で)明らかに不正確なアプリケーションを実行し続けているためです。呼び出し元がAssertionError
(またはError
またはThrowable
)をキャッチして処理する可能性は確かに低くなりますが、もちろん呼び出し元は好きなことを何でもできます。
私の意見では、AssertionError
をここで使用するのは正しくありません。
ドキュメントから 、AssertionErrorは基本クラスを拡張します Error
Errorは、Throwableのサブクラスであり、妥当なアプリケーションがキャッチしようとしてはならない重大な問題を示します。
エラーは致命的であるはずですが、プログラムがこれを処理し、ユーザーに不明な操作に関する警告メッセージを表示することを期待します。
ここに何かあれば、 UnsupportedOperationException
がスローされ、コールスタックの別の場所で処理されることを期待します。
要求された操作がサポートされていないことを示すためにスローされます。
電卓ではなく、ENUMを使用するコードフローの場合を考えてみます。
開発者が既存の列挙型に新しい値を追加する場合、新しい値がサポートされていないという理由だけで、この既存の列挙型を使用する関数がエラーを呼び出すことは期待できません。
エラーに関して、 Javaチュートリアル は次のように述べています。
2番目の例外はエラーです。これらは、アプリケーションにとってexternalであり、アプリケーションが通常は予測または回復できない例外的な状態です。
また、 アサーションを使用したプログラミング ガイドには次のように記載されています。
パブリックメソッドの引数チェックにアサーションを使用しないでください。
ですから、例外はこの種のケースをチェックする正しい方法だと思います。
new UnsupportedOperationException("Operator " + name() + " is not supported.");
を使用することをお勧めします。私の意見では問題をよりよく説明しているためです(つまり、開発者は列挙値を追加しましたが、必要なケースを実装するのを忘れていました)。
ただし、このサンプルケースでは、スイッチの代わりにAbstractEnum
デザインパターンを使用する必要があると思います。
PLUS {
double apply(double x, double y) {
return x + y;
}
},
MINUS {
double apply(double x, double y) {
return x - y;
}
},
TIMES {
double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
このコードは、すべてのケースがapply
を実装するまでコンパイルされないため、エラーが発生しにくくなります。
を好む
double apply(double x, double y) {
switch(this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
default: assert this==DIVIDE: return x / y;
}
}
AssertionError
をスローするべきではありません。しかし、私は最も好む https://stackoverflow.com/a/41324246/348975
ここではAssertionErrorとIllegalAEの両方があまり良くないと思います。 Mattの回答に示されているように、アサーションエラーは適切ではありません。そして引数はここでは間違っていません。それらは間違ったthis
操作でメソッドに渡されるだけです。 IAEも良いとは限りません。もちろん、これは意見ベースの質問と回答でもあります。
また、AssertionErrorをスローするためにアサーションを有効にすることが必須であるかどうか、またはassertionErrorがアサーションが有効になったことを意味するかどうかはわかりません。
私の理解では、あなたのメソッドはenumオブジェクトのメソッドです。ほとんどの場合、誰かが新しい列挙値を追加するとき、彼は「適用」メソッドも変更する必要があります。この場合はUnsupportedOperationExceptionをスローする必要があります。