web-dev-qa-db-ja.com

メソッドが複数のタイプのチェック済み例外をスローしないのはなぜですか?

SonarQubeを使用してJavaコードを分析し、このルールがあります(クリティカルに設定)):

パブリックメソッドは最大1つのチェック済み例外をスローする必要があります

チェックされた例外を使用すると、メソッドの呼び出し元は、エラーを伝播または処理することによってエラーに対処する必要があります。これにより、これらの例外はメソッドのAPIの一部となります。

呼び出し側の複雑さを合理的に保つために、メソッドは複数の種類のチェック例外をスローするべきではありません。」

ソナーの別のビットには this があります:

パブリックメソッドは最大1つのチェック済み例外をスローする必要があります

チェックされた例外を使用すると、メソッドの呼び出し元は、エラーを伝播または処理することによってエラーに対処する必要があります。これにより、これらの例外はメソッドのAPIの一部となります。

呼び出し側の複雑さを合理的に保つために、メソッドは複数の種類のチェック例外をスローするべきではありません。

次のコード:

public void delete() throws IOException, SQLException {      // Non-Compliant
  /* ... */
}

にリファクタリングする必要があります:

public void delete() throws SomeApplicationLevelException {  // Compliant
    /* ... */
}

オーバーライドするメソッドはこのルールではチェックされず、チェックされた例外をいくつかスローすることができます。

私は例外処理に関する私のリーディングでこのルール/推奨事項に出くわしたことがなく、このトピックに関するいくつかの標準や議論などを見つけようとしました。私が見つけた唯一のものは、CodeRachからのこれです: メソッドがスローする必要のある例外の数はいくつですか?

これは広く受け入れられている標準ですか?

47
sdoca

次のコードが提供されている状況を考えてみましょう。

_public void delete() throws IOException, SQLException {      // Non-Compliant
  /* ... */
}
_

ここでの危険は、delete()を呼び出すために作成するコードが次のようになることです。

_try {
  foo.delete()
} catch (Exception e) {
  /* ... */
}
_

これも悪いです。そして、ベースとなるExceptionクラスをキャッチするフラグを立てる別のルールでキャッチされます。

重要なのは、悪いコードを他の場所に書きたくなるようなコードを書かないことです。

あなたが遭遇しているルールはかなり一般的なものです。 Checkstyle のデザインルールに含まれています。

ThrowsCount

Throwsステートメントを指定した数(デフォルトでは1)に制限します。

理論的根拠:例外はメソッドのインターフェースの一部を形成します。あまりにも多くの異なるルートの例外をスローするようにメソッドを宣言すると、例外処理が面倒になり、catch(Exception ex)のようなコードを書くなどのプログラミングプラクティスが不十分になります。このチェックにより、開発者は例外を階層に入れ、最も単純なケースでは、呼び出し元がチェックする必要がある例外は1種類のみですが、必要に応じてサブクラスを明確にキャッチできます。

これは問題を正確に説明し、問題とは何か、なぜそれをすべきでないのかを説明します。多くの静的分析ツールが識別してフラグを付けることは、広く受け入れられている標準です。

そして、あなたがかもしれない言語設計に従ってそれをする、そしてそこにあるかもしれないそれが正しいことである時がある、それは見てすぐに「うーん、なぜ私はこれをしているの?」誰もがcatch (Exception e) {}をしないように十分に訓練されている内部コードでは受け入れられるかもしれませんが、特に内部の状況で人々が手抜きをするのを見たことがあるでしょう。

クラスを使用している人が悪いコードを書きたがらないようにしてください。


Java SE 7以降では、1つのcatchステートメントで複数の例外をキャッチできるため、この重要性は低くなります( 複数の例外タイプをキャッチし、タイプを改善して例外を再スローするチェック Oracleから)。

Java 6以前では、次のようなコードになります。

_public void delete() throws IOException, SQLException {
  /* ... */
}
_

そして

_try {
  foo.delete()
} catch (IOException ex) {
     logger.log(ex);
     throw ex;
} catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}
_

または

_try {
    foo.delete()
} catch (Exception ex) {
    logger.log(ex);
    throw ex;
}
_

Java 6のこれらのオプションはどちらも理想的ではありません。最初のアプローチは [〜#〜] dry [〜#〜] に違反します。複数のブロックが同じことを繰り返します。繰り返します-例外ごとに1回です。例外をログに記録して再スローしますか?わかりました。例外ごとに同じコード行です。

2番目のオプションは、いくつかの理由で悪くなります。まず、すべての例外をキャッチしているということです。そこにヌルポインターがキャッチされます(そして、キャッチされるべきではありません)。さらに、Exceptionを再スローしています。これは、メソッドシグネチャがdeleteSomething() throws Exceptionになることを意味します。これにより、コードを使用している人々が現在のようにスタックをさらに混乱させる forcedcatch(Exception e)に変更します。

Java 7の場合、代わりに次のことができるため、これはasとして重要ではありません。

_catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}
_

さらに、スローされている例外のタイプを1つが行うかどうかをタイプチェックがキャッチします。

_public void rethrowException(String exceptionName)
throws IOException, SQLException {
    try {
        foo.delete();
    } catch (Exception e) {
        throw e;
    }
}
_

型チェッカーは、eIOExceptionまたはSQLExceptionの型のみであることを認識します。私はまだこのスタイルの使用に過度に熱心ではありませんが、Java 6(メソッドシグネチャを強制することになる)の場合と同じように、悪いコードを引き起こしていません。例外が拡張するスーパークラス)。

これらすべての変更にもかかわらず、多くの静的分析ツール(Sonar、PMD、Checkstyle)は、Java 6スタイルガイドを実施しています。これは悪いことではありません。これらの警告に同意する傾向がありますstillが強制されますが、チームの優先順位に応じて、優先順位をメジャーまたはマイナーに変更できます。

例外をチェックするかチェックしないかは...の問題です greatdebate 無数のブログを簡単に見つけることができる議論の両側を取り上げる投稿。ただし、チェック済みの例外を使用している場合は、少なくともJava 6.の下では、複数の型をスローすることを避ける必要があります。

32
user40980

理想的には、1種類の例外のみをスローする理由は、そうしないと 単一の責任 および 依存関係の逆転 の原則に違反する可能性があるためです。例を使って説明しましょう。

永続性からデータをフェッチするメソッドがあり、永続性はファイルのセットであるとしましょう。ファイルを扱っているので、FileNotFoundExceptionを使用できます。

public String getData(int id) throws FileNotFoundException

これで要件が変更され、データはデータベースから取得されます。 FileNotFoundExceptionの代わりに(ファイルを扱っていないため)、SQLExceptionをスローします。

public String getData(int id) throws SQLException

メソッドを使用するすべてのコードを調べ、チェックする必要がある例外を変更する必要があります。そうしないと、コードがコンパイルされません。私たちのメソッドが広範囲に呼び出された場合、それを変更したり、他の人に変更を加えたりするのは大変なことです。それには長い時間がかかり、人々は幸せになることはありません。

依存関係の逆転は、カプセル化に取り組んでいる内部実装の詳細を公開しているため、これらの例外のいずれも実際にスローすべきではないと述べています。呼び出しコードは、レコードを取得できるかどうかを本当に心配する必要がある場合に、使用している永続性のタイプを知る必要があります。代わりに、APIを通じて公開しているのと同じレベルの抽象化でエラーを伝える例外をスローする必要があります。

public String getData(int id) throws InvalidRecordException

ここで、内部実装を変更した場合、その例外をInvalidRecordExceptionでラップして渡すだけでかまいません(またはラップせずに、新しいInvalidRecordExceptionをスローするだけです)。外部コードは、どのタイプの永続性が使用されているのかを知りません。すべてカプセル化されています。


単一の責任については、複数の無関係な例外をスローするコードについて考える必要があります。次のメソッドがあるとします。

public Record parseFile(String filename) throws IOException, ParseException

この方法について何が言えますか?署名から、ファイルを開き解析することがわかります。メソッドの説明で「and」や「or」などの接続詞を見ると、それが複数のことをしていることがわかります。複数の責任があります。複数の責任を持つメソッドは、責任のいずれかが変更されると変更される可能性があるため、管理が困難です。代わりに、メソッドを分割して単一の責任を持たせる必要があります。

public String readFile(String filename) throws IOException
public Record parse(String data) throws ParseException

データを解析する責任からファイルを読み取る責任を抽出しました。これの副作用の1つは、任意の文字列データを任意のソース(インメモリ、ファイル、ネットワークなど)から解析データに渡すことができるようになることです。また、ファイルを必要としないため、parseをより簡単にテストできるテストを実行するディスク。


メソッドからスローできる例外が2つ(またはそれ以上)ある場合もありますが、SRPとDIPに固執すると、この状況に遭遇することがまれになります。

22
cbojar

Java=で遊んだとき、これを少しいじったことを覚えていますが、あなたの質問を読むまで、チェック済みとチェックなしの違いを実際には意識していませんでした。この記事を見つけましたグーグルですぐに、そしてそれは明白な論争のいくつかに入ります:

http://tutorials.jenkov.com/Java-exception-handling/checked-or-unchecked-exceptions.html

そうは言っても、この例外がチェックされた例外について言及している問題の1つは、チェックされた例外の束をthrows句に追加し続けると(そして私は最初からJavaでこれに遭遇しました)、あなたのメソッド宣言は、より高いレベルのメソッドに移動するときにそれをサポートするために多くのボイラープレートコードを挿入する必要があるだけでなく、より多くの例外タイプを導入しようとすると、混乱が大きくなり、互換性が失われますレベルのメソッド。チェックされた例外タイプを下位レベルのメソッドに追加する場合は、コードを実行して、他のいくつかのメソッド宣言も調整する必要があります。

記事で言及された緩和策の1つのポイント-そして著者が個人的にこれを好きではなかった-は、基本クラスの例外を作成し、throws句をそれだけを使用するように制限して、内部でそのサブクラスを発生させることでした。これにより、すべてのコードを実行し直すことなく、新しいチェック済み例外タイプを作成できます。

この記事の著者はそれほど好きではなかったかもしれませんが、私の個人的な経験では完全に理にかなっています(特に、すべてのサブクラスが何であるかを調べることができる場合)、そしてそれがあなたに与えられているアドバイスですすべてを1つのチェック済み例外タイプにそれぞれキャップします。さらに、あなたが言及したアドバイスは、非公開メソッドで複数のチェック済み例外タイプを実際に許可していることです。とにかくそれがプライベートメソッドまたは同様の何かである場合、1つの小さな変更でコードベースの半分を実行することはありません。

あなたはこれが受け入れられる標準であるかどうかを主に尋ねましたが、あなたの言及したこの合理的に考え抜かれた記事と、個人的なプログラミング経験から言えば、確かにそれは決して際立っていないようです。

3
Panzercrisis