web-dev-qa-db-ja.com

Javaグッドプラクティスで積極的にAssertionErrorをスローしていますか?

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


明確にするために編集:私の質問は、Errorhereをスローするかどうかではなく、ExceptionまたはErrorをスローする場合、どちらを選択する必要がありますか?そして、AssertionErrorsを積極的にスローすることは良い習慣ですか?ドキュメントにはアサーションが失敗したことを示すためにスローされますとあるので、積極的にスローするべきではないと感じています。あれは正しいですか?


2番目の編集:明確な質問:AssertionErrorを積極的にスローすることは良い習慣ですか、それが可能であっても、それを避けるべきですか? (ドキュメントを読んでいる私の推測は後者です)

22
Mathias Bader

私はここでブロッホ氏に同意します-代替案(IllegalArgumentExceptionIllegalStateException、およびUnsupportedOperationException)は問題の重大度を適切に伝えておらず、発信者は誤ってこのケースをキャッチして処理します。実際、この行に到達した場合、問題のプログラムはbrokenであり、唯一の正気なことは終了することです。

ここでのポイントは、列挙型には有限の値のセットがあるため、throw行に到達することは不可能であるべきです-このインスタンスメソッドを修正せずに列挙型の定義が変更された場合にのみ発生します。 RuntimeExceptionをスローすると、実際にはメソッド(および列挙型)自体が壊れているときに、呼び出し元がミスをしたことを示しています。 AssertionErrorを明示的に発生させることは、このメソッドが予期する不変式が違反されていることを示します。

グアバには、ブレークダウンについての役立つ記事があります さまざまなタイプの例外を発生させる場合 。彼らは書きます:

従来のアサーションは、クラス自体(チェックを含む)が何らかの方法で壊れている場合にのみ失敗するチェックです。 (場合によっては、これがパッケージにまで及ぶ可能性があります。)これらは、後置条件、クラス不変式、および内部前提条件(非パブリックメソッド)を含むさまざまな形式をとることができます。

不可能な条件のチェックは、周囲のコードが後で変更されない限り、またはプラットフォームの動作に関する最も深い前提に著しく違反しない限り、失敗する可能性がないものです。これらは不必要なはずですが、ステートメントが到達不可能であることをコンパイラーが認識できないため、またはコンパイラーが推測できない制御フローについて何かを知っているため、しばしば強制されます。

このページでは、AssertionErrorがこれらのケースを処理するための推奨される方法であると述べています。 Verify クラスのコメントも、例外の選択に関するいくつかの有用な洞察を提供します。 AssertionErrorが強すぎると思われる場合、VerifyExceptionを上げることは良い妥協案です。

ErrorまたはRuntimeExceptionの特定の質問については、それは本当に問題ではありません(どちらもオフになっているため、キャッチされずに呼び出しスタックを上に移動する可能性があります)が、呼び出し元はRuntimeExceptionからの回復を試みます。このような場合のアプリケーションのクラッシュは機能です。それ以外の場合は、(現時点で)明らかに不正確なアプリケーションを実行し続けているためです。呼び出し元がAssertionError(またはErrorまたはThrowable)をキャッチして処理する可能性は確かに低くなりますが、もちろん呼び出し元は好きなことを何でもできます。

19
dimo414

私の意見では、AssertionErrorをここで使用するのは正しくありません。

ドキュメントからAssertionErrorは基本クラスを拡張します Error

Errorは、Throwableのサブクラスであり、妥当なアプリケーションがキャッチしようとしてはならない重大な問題を示します。

エラーは致命的であるはずですが、プログラムがこれを処理し、ユーザーに不明な操作に関する警告メッセージを表示することを期待します。

ここに何かあれば、 UnsupportedOperationException がスローされ、コールスタックの別の場所で処理されることを期待します。

要求された操作がサポートされていないことを示すためにスローされます。

電卓ではなく、ENUMを使用するコードフローの場合を考えてみます。

開発者が既存の列挙型に新しい値を追加する場合、新しい値がサポートされていないという理由だけで、この既存の列挙型を使用する関数がエラーを呼び出すことは期待できません。

5
Matt Clark

エラーに関して、 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を実装するまでコンパイルされないため、エラーが発生しにくくなります。

4
Raphaël

を好む

    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;
    }
}
  1. 実際のアサーション用に予約する必要があるため、AssertionErrorをスローするべきではありません。
  2. アサーションと一部のキャッチブロック以外に、実際には到達できないコードが1つもないはずです。

しかし、私は最も好む https://stackoverflow.com/a/41324246/348975

2
emory

ここではAssertionErrorとIllegalAEの両方があまり良くないと思います。 Mattの回答に示されているように、アサーションエラーは適切ではありません。そして引数はここでは間違っていません。それらは間違ったthis操作でメソッドに渡されるだけです。 IAEも良いとは限りません。もちろん、これは意見ベースの質問と回答でもあります。

また、AssertionErrorをスローするためにアサーションを有効にすることが必須であるかどうか、またはassertionErrorがアサーションが有効になったことを意味するかどうかはわかりません。

1
Atul

私の理解では、あなたのメソッドはenumオブジェクトのメソッドです。ほとんどの場合、誰かが新しい列挙値を追加するとき、彼は「適用」メソッドも変更する必要があります。この場合はUnsupportedOperationExceptionをスローする必要があります。

0
Ivan Dubynets