チェックされた例外をキャッチして、RuntimeExceptionをスローすることをお勧めしますか?


70

同僚のコードを読んだところ、彼はさまざまな例外を頻繁にキャッチし、代わりに常に「RuntimeException」をスローすることがわかりました。これは非常に悪い習慣だといつも思っていました。私が間違っている?


17
「チェック済み例外の価格はオープン/クローズド原則違反です。コード内のメソッドからチェック済み例外をスローし、catchが上記の3レベル上にある場合、その例外をユーザーとcatchの間の各メソッドのシグネチャで宣言する必要がありますこれは、ソフトウェアの低レベルでの変更により、多くの高レベルで署名の変更を強制できることを意味します。」—ロバートC.マーティン、«クリーンコード»、107ページ
Songo 14年

6
興味深いのは、Jim Waldoが「Java:The Good Parts」shop.oreilly.com/product/9780596803742.doの未チェックの例外に対して暴言を唱えているということです。わずか6年前に公開されたJUGで読みましたが、良いアドバイスのように思えました!現在、関数型プログラミングでは、チェック例外は完全に扱いにくいです。ScalaやKotlinのような言語にはそれらさえありません。未チェックの例外でチェックインのラッピングを開始しました。
グレンペターソン

@GlenPetersonには、実行を完全に回避し、代わりに合計タイプ
jk)

機能インタフェースの明白な場合もあります:組み込み機能インタフェース(すなわちFunctionPredicateパラメータ化されていない、等)throws節。これは、stream()メソッドの内側のループで、チェックされた例外をキャッチ、ラップ、および再スローする必要があることを意味します。それ自体が、チェックされた例外が悪い考えであるかどうかについてのバランスを私に与えます。
ジョエルコルネット

例外を通じて意味を伝えるために、RuntimeExceptionのカスタムサブクラスを作成しても何も問題はありません。
ジョエルコルネット

回答:


55

あなたの同僚が何か間違ったことをしているかどうかを知るのに十分な文脈がわからないので、一般的な意味でこれについて議論します。

私は、チェック例外をランタイム例外のフレーバーに変えることは常に間違った慣行だとは思わないチェックされた例外は、多くの場合、開発者によって悪用れ、悪用されます。

チェック例外は、使用することを意図していない場合(回復不可能な条件、または制御フローでさえも)に使用するのが非常に簡単です。特に、呼び出し元が回復できない条件にチェック例外が使用されている場合、有用なメッセージ/状態を持つランタイム例外にその例外を変更することは正当化されると思います。残念ながら、多くの場合、回復不可能な状態に直面すると、空のcatchブロックを使用する傾向があります。これは、実行可能な最悪のことの1つです。このような問題をデバッグすることは、開発者が直面する最大の苦痛の1つです。

したがって、回復可能な状態を処理していると思われる場合は、それに応じて処理する必要があり、例外をランタイム例外に変換しないでください。チェックされた例外が回復不能な条件に使用される場合、ランタイム例外に変更することは正当化されます。


17
ほとんどの実際のアプリケーションでは、回復不能な状態はほとんどありません。「OK、このアクションは失敗しました。すてきなエラーメッセージを表示/ログに記録し、次のメッセージを続行/待機する」と言うことができるレベルがほぼあります。
マイケルボルグワード

6
@MichaelBorgwardtはその通りですが、そのような処理の場所はアプリケーションの最上位レベルであることが多いため、開発者が下位レベルで例外を「処理」するのを見ると、通常、処理を削除して例外を浸透させるだけです。上向き。たとえば、JSFのようなWebフレームワークは、最高レベルで例外をキャッチし、ログメッセージを出力し、他のリクエストの処理を続けます(デフォルトの処理が適切であるとは言わず、単なる例です)。
-DavidS

40

それは良いことができます。読んでください:

http://onjava.com/pub/a/onjava/2003/11/19/exceptions.html

ほとんどの場合、クライアントコードはSQLExceptionsについて何もできません。それらを未チェックの例外に変換することをheしないでください。次のコードを検討してください。

public void dataAccessCode(){
  try{
      ..some code that throws SQLException
  }catch(SQLException ex){
      ex.printStacktrace();
  }
} 

このcatchブロックは例外を抑制し、何もしません。正当な理由は、クライアントがSQLExceptionについてできることは何もないということです。次の方法で対処してみてはいかがですか?

public void dataAccessCode(){
   try{
       ..some code that throws SQLException
   }catch(SQLException ex){
       throw new RuntimeException(ex);
   }
} 

これにより、SQLExceptionがRuntimeExceptionに変換されます。SQLExceptionが発生すると、catch句は新しいRuntimeExceptionをスローします。実行スレッドは中断され、例外が報告されます。ただし、特にSQLExceptionについては何もできないため、不必要な例外処理でビジネスオブジェクトレイヤーを破損することはありません。キャッチに例外の根本原因が必要な場合は、JDK1.4以降、すべての例外クラスで使用可能なgetCause()メソッドを使用できます。

チェック済み例外をスローし、それから回復できないことは役に立ちません。

一部の人々は、チェック例外をまったく使用すべきでないとさえ考えています。http://www.ibm.com/developerworks/java/library/j-jtp05254/index.htmlを参照して ください

最近、ブルース・エクケルやロッド・ジョンソンなどの著名な専門家が、チェック例外に関する正統的な立場に当初は完全に同意したが、チェック例外を排他的に使用することはそれほど良い考えではないと結論付けました。最初に登場し、そのチェックされた例外は、多くの大規模プロジェクトの重大な問題の原因になっています。Eckelはより極端な見方をしており、すべての例外をチェックしないでください。ジョンソンの見解はより保守的ですが、チェックされた例外に対する正統的な選好が過剰であることを依然として示唆しています。(C#のアーキテクトは、Javaテクノロジの使用経験がほぼ確実であったため、言語設計からチェック例外を省略し、すべての例外をチェックなしの例外にしたことに注意してください。

また、同じリンクから:

未チェックの例外を使用する決定は複雑なものであり、明らかな答えがないことは明らかです。Sunのアドバイスは何も使用しないことであり、C#アプローチ(Eckelと他の人が同意する)は、すべてに使用することです。他の人は、「妥協点がある」と言います。


13

いいえ、あなたは間違っていません。彼の練習は非常に見当違いです。その原因となった問題を捕捉する例外をスローする必要があります。RunTimeExceptionは広範にわたるものです。NullPointerException、ArgumentExceptionなどである必要があります。何が間違っていたかを正確に説明します。これにより、処理する必要がある問題を区別し、「パスしないでください」シナリオであるエラーに対してプログラムを存続させることができます。彼がしていることは、質問で提供された情報に何か足りないものがない限り、「On Error Resume Next」よりわずかに優れています。


1
ヒントをありがとう。また、RuntimeExceptionから直接継承する、実装済みのカスタム例外をスローするとどうなりますか?
RoflcoptrException

27
@Gary Buynは:多くの人がチェック例外が失敗した言語設計の実験であることを考えると、彼らはない習慣の問題として、慎重に使用する必要がありますものです。
マイケルボルグワード

7
@Gary Buyn:ここではかなりよく議論を概説記事です:ibm.com/developerworks/java/library/j-jtp05254/index.html注意もJavaがこの機能を導入し15年以上の後、他の言語がそれを採用しなかったことは、 C ++は同様の機能を廃止しました。
マイケルボルグワード

7
@c_maker:実際には、Blochは主にオーソドックスな見方を促進していますが、あなたのコメントは主に、より多くの例外、期間を使用することについてのようです。私の見解では、チェック済み例外を使用する唯一の有効な理由は、すべての呼び出し元がすぐに処理することを期待する条件のためです。
マイケルボルグワード

14
不要なチェック済み例外をスローすると、カプセル化に違反します。「getAccounts()」などの単純なメソッドが「SQLException」、「NullPointerException」、または「FileNotFoundException」をスローした場合、どうしますか?対処できますか?おそらく 'catch(Exception e){}'でしょう。その上、それらの例外-その実装固有です!契約の一部であってはなりません!知っておく必要があるのは、エラーがあったことだけです。そして、実装が変更された場合はどうなりますか?突然、すべてを変更する必要があり、メソッドは「SQLException」をスローせず、代わりに「ParseXMLException」をスローします!
KL

8

場合によります。

この習慣は賢明でさえあるかもしれません。いくつかの例外が発生した場合、あなたは何もできません(たとえば、あなたのコードから矛盾したDBを修復できないため、開発者だけがそれを行うことができます)、多くの状況があります(例えば、ウェブ開発)。これらの状況では、スローされた例外をランタイム例外にラップして再スローするのが賢明です。いくつかの例外処理レイヤーでこれらの例外をすべてキャッチし、エラーを記録して、ユーザーにローカライズされたエラーコード+メッセージを表示することができます。

一方、例外がランタイムでない(チェックされている)場合、APIの開発者は、この例外は解決可能であり、修復する必要があることを示します。可能であれば、間違いなくそれを行う必要があります。

他の解決策は、このチェックされた例外を呼び出し層に再スローすることかもしれませんが、例外が発生した場所でそれを解決できなかった場合は、ここでも解決できない可能性があります...


APIの開発者が自分が何をしているかを知っており、チェック例外をうまく使用することを望みます。クライアントが必要に応じてキャッチするオプションを持つように、ランタイム例外をスローすることを好むAPIをドキュメント化し始めました。
c_maker

一般に、例外をスローするメソッドは、呼び出し元が例外から回復するかどうかを知ることができません。一方、内側のメソッドが例外をスローした理由がわかっていて、その理由が外側のメソッドのAPIと一致している場合、メソッドは、内側のメソッドによってスローされたチェック済み例外のみをエスケープすることをお勧めします。内部メソッドが予期せずにチェック例外をスローした場合、チェック例外としてそれをバブルさせると、呼び出し元に何が起こったのかが誤って通知される可能性があります。
supercat

2
言及してくれてありがとうexception handling layer-例えば、webapp、フィルターで。
ジェイクトロント14年

5

これについてコメントをもらいたいのですが、必ずしも悪い習慣とは限らない場合があります。(またはひどく悪い)。しかし、多分私は間違っています。

多くの場合、使用しているAPIは、特定のユースケースで実際にスローされるとは想像できない例外をスローします。この場合、原因としてキャッチされた例外を指定してRuntimeExceptionをスローしても問題ありません。この例外がスローされた場合、プログラミングエラーの原因である可能性が高く、正しい指定の範囲内にありません。

RuntimeExceptionが後でキャッチされて無視されないと仮定すると、OnErrorResumeNextの近くにはありません。

OnErrorResumeNextは、誰かが例外をキャッチし、単にそれを無視するか、単に出力するときに発生します。これはほとんどすべての場合にひどく悪い習慣です。


これは、コールツリーの最上部近くで発生する可能性があります。ここでできることは、正常に回復しようとすることと、特定のエラーが実際には役に立たないことです。その場合、エラーを記録して先へ進む必要があります(次のレコードを処理し、エラーが発生したことをユーザーに通知するなど)。そうでなければ、いいえ。次のハンドラーのために白い象として例外をラップするのではなく、常にエラーに近い実用的な例外を処理する必要があります。
マイケルK

@MichaelK問題は、実際には「実用的な範囲でエラーに近い」ということは、多くの場合「直接制御できない複数の介在層を介して」という意味です。たとえば、クラスで特定のインターフェイスを実装する必要がある場合、手が縛られます。これは、呼び出しツリーの任意の深さで発生する可能性があります。インターフェースが私の制御下にある場合でも、スロー宣言を追加すると、おそらくスローできる具体的な実装のセットが限られている場合、抽象化が漏れやすくなります。少数の実装の詳細に対してすべてのクライアントにコストを支払うことは、IMOの優れた設計トレードオフではありません。
ティムセギーン

4

TL; DR

前提

  • エラーが回復不可能な場合、つまり、エラーがコード内にあり、外部状態に依存しない場合(つまり、回復によりコードが修正される場合)、ランタイム例外をスローする必要があります。
  • チェックされた例外は、コードが正しいが、外部状態が期待どおりではない場合にスローされる必要があります。ネットワーク接続がない、ファイルが見つからない、または破損しているなどです。

結論

伝播コードまたはインターフェイスコードで、基礎となる実装が外部状態に依存していると想定している場合、明らかに依存していない場合、チェック例外をランタイム例外として再スローすることがあります。


このセクションでは、いずれかの例外をスローする必要がある場合のトピックについて説明します。結論のより詳細な説明を読みたい場合は、次の水平バーにスキップできます。

ランタイム例外をスローするのはいつが適切ですか?コードが正しくないことが明らかで、コードを変更することで回復が適切である場合、ランタイム例外をスローします。

たとえば、次の場合はランタイム例外をスローするのが適切です。

float nan = 1/0;

これにより、ゼロ除算ランタイム例外がスローされます。コードに欠陥があるため、これは適切です。

または、たとえば、HashMapのコンストラクタの一部を次に示します。

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                loadFactor);
    // more irrelevant code...
}

初期容量または負荷係数を修正するには、コードを編集して正しい値が渡されるようにするのが適切です。これは、遠く離れたサーバーが稼働していること、ディスクの現在の状態、ファイル、または別のプログラム。無効な引数で呼び出されたコンストラクターは、無効なパラメーターまたはエラーを見逃した不適切なフローを引き起こした間違った計算であっても、呼び出し元のコードの正確さに依存します。

いつチェック例外をスローするのが適切ですか?コードを変更せずに問題回復できる場合、チェック済み例外をスローします。別の言い方をすれば、コードが正しい状態でエラーが状態に関連している場合は、チェック済み例外をスローします。

ここで、「回復」という言葉は扱いにくいかもしれません。これは、目標を達成する別の方法を見つけることを意味する場合があります。たとえば、サーバーが応答しない場合は、次のサーバーを試す必要があります。あなたのケースでそのような回復が可能な場合、それは素晴らしいですが、回復が意味するのはそれだけではありません-回復は単に何が起こったのかを説明するエラーダイアログをユーザーに表示するか、それがサーバーアプリケーションである場合管理者にメールを送信するか、エラーを適切かつ簡潔に記録するだけです。

mrmugglesの回答で言及された例を見てみましょう。

public void dataAccessCode(){
   try{
       ..some code that throws SQLException
   }catch(SQLException ex){
       throw new RuntimeException(ex);
   }
}

これは、チェック済み例外を処理する正しい方法ではありません。このメソッドのスコープで例外を処理するだけの能力がないからといって、アプリがクラッシュする必要があるという意味ではありません。代わりに、次のように上位のスコープに伝達するのが適切です。

public Data dataAccessCode() throws SQLException {
    // some code that communicates with the database
}

これにより、呼び出し元による回復の可能性が考慮されます。

public void loadDataAndShowUi() {
    try {
        Data data = dataAccessCode();
        showUiForData(data);
    } catch(SQLException e) {
        // Recover by showing an error alert dialog
        showCantLoadDataErrorDialog();
    }
}

チェック例外は静的分析ツールであり、実装を学習したり試行錯誤プロセスを実行したりすることなく、特定の呼び出しで何がうまくいかないかをプログラマーに明らかにします。これにより、エラーフローのどの部分も無視されないことを簡単に確認できます。実行時例外としてチェック済み例外を再スローすると、この省力化された静的分析機能に対して機能します。

また、呼び出し層は、上で示したように、より大きなスキームのより良いコンテキストを持っていることに言及する価値があります。dataAccessCode呼び出される原因は多くありますが、呼び出しの特定の理由は呼び出し元にのみ表示されます。したがって、障害が発生した場合の正しい回復時に、より適切な判断を下すことができます。

この区別が明確になったので、チェック例外をランタイム例外として再スローしてもよいかどうかを推測することに進むことができます。


上記を考えると、いつチェック例外をRuntimeExceptionとして再スローするのが適切でしょうか?使用しているコードが外部状態に依存していると仮定した場合、外部状態に依存していないことを明確に主張できます。

以下を考慮してください。

StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
    doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
    throw new IllegalStateException(e);
}

この例ではIOException、APIはReader外部状態にアクセスするように設計されているため、コードは伝播していますが、StringReader実装は外部状態にアクセスしないことがわかっています。このスコープでは、呼び出しに関係する部分がIOや他の外部状態にアクセスしないことを確かに主張できますが、実装を知らない同僚を驚かせることなく、例外をランタイム例外として安全に再スローできます(おそらくIOアクセスコードがIOException)をスローすると仮定します。


外部状態依存の例外を厳密にチェックし続ける理由は、それらが非決定的であるためです(コードのバージョンごとに予測可能に再現されるロジック依存の例外とは異なります)。たとえば、0で除算しようとすると、常に例外が発生します。0で除算しないと、例外が発生することはありません。また、例外が発生することはないため、例外を処理する必要はありません。ただし、ファイルにアクセスする場合、一度成功しても次に成功するとは限りません。ユーザーがアクセス許可を変更したり、別のプロセスがそのファイルを削除または変更した可能性があります。そのため、例外的なケースを常に処理する必要があります。そうしないと、バグが発生する可能性があります。


2

スタンドアロンアプリケーション用。アプリケーションが例外を処理できないことがわかっている場合、チェックされたRuntimeExceptionをスローする代わりにエラーをスローし、アプリケーションをクラッシュさせ、バグレポートを期待し、アプリケーションを修正します。(チェック済みと未チェックの長所短所の詳細については、mrmuggles回答を参照してください。)


2

これは多くのフレームワークで一般的な方法です。たとえば、Hibernateまさにこれを行います。APIは、API Exceptionを呼び出す場所でそれらを処理するために明示的にコードを記述する必要があるため、APIはクライアント側の邪魔にならず、侵入的であるべきです。しかし、そもそも例外を処理する適切な場所ではないかもしれません。
正直に言うと、これは「ホット」なトピックであり、多くの論争であるので、私は味方しませんが、あなたの友人が行う/提案することは珍しいことでも珍しいことでもありません。


1

「チェック済み例外」全体は悪い考えです。

構造化プログラミングでは、「近く」にある関数(またはJavaの用語ではメソッド)の間でのみ情報を渡すことができます。より正確には、情報は次の2つの方法でのみ機能間を移動できます。

  1. 引数の受け渡しによる、呼び出し元から呼び出し先へ。

  2. 戻り値としての呼び出し先から呼び出し元へ。

これは根本的に良いことです。これにより、ローカルでコードについて推論できます。プログラムの一部を理解または変更する必要がある場合は、その部分と他の「近くの」部分だけを見る必要があります。

ただし、状況によっては、中間の誰もが「認識」せずに「遠隔」機能に情報を送信する必要があります。これは、例外を使用する必要がある場合です。例外は、レイザー(コードのどの部分にもステートメントが含まれている可能性がある)からハンドラー(コードのどの部分にn であった例外と互換性のあるブロックが含まれている可能性がある)に送信される秘密メッセージです。throwcatchthrow

チェックされた例外は、メカニズムの秘密を破壊し、それによって、その存在のまさにその理由を破壊します。関数が呼び出し元に情報を「知らせる」余裕がある場合は、その情報を戻り値の一部として直接送信するだけです。


メソッドが呼び出し元によって提供された関数を実行する場合、この種の問題が実際に大混乱を引き起こす可能性があることに言及するのは良いかもしれません。関数を受け取るメソッドの作成者は、多くの場合、呼び出し元が何を期待しているのか、また呼び出し元がどのような例外を期待しているのかを知る必要も気にする必要もありません。メソッドを受け取るコードがチェック例外をスローすることを期待していない場合、提供されるメソッドは、サプライヤがキャッチできる未チェック例外をスローするチェック例外をラップする必要があります。
supercat

0

これは、ケースバイケースに依存する場合があります。特定のシナリオでは、友人がやっていることを行うのが賢明です。たとえば、一部のクライアントにAPIを公開していて、特定の実装例外が実装の詳細であり、クライアントには公開できません。

チェックされた例外を邪魔にならないようにすることで、クライアント自体が例外条件を事前検証している可能性があるため、クライアントがよりクリーンなコードを作成できるようにするAPIを公開できます。

たとえば、Integer.parseInt(String)は文字列を受け取り、それに相当する整数を返し、文字列が数値でない場合にNumberFormatExceptionをスローします。ここで、フィールドを含むフォームage送信がこのメソッドによって変換されますが、クライアントはすでにその部分で検証を保証しているので、例外のチェックを強制する意味はありません。


0

ここにはいくつかの質問があります

  1. チェック済み例外を未チェック例外に変換する必要がありますか?

一般的な経験則では、呼び出し元がキャッチして回復することが期待される例外をチェックする必要があります。他の例外(操作全体を中止することが唯一の妥当な結果である場合、またはそれらの処理を特に心配する価値がないほど十分に低いと考える場合)はオフにする必要があります。

例外がキャッチおよびリカバリに値するかどうかの判断が、使用しているAPIの判断と異なる場合があります。コンテキストが重要な場合があり、ある状況で処理する価値がある例外は、別の状況で処理する価値がない場合があります。時々、あなたの手は既存のインターフェースによって強制されます。そのため、チェック済み例外を非チェック済み例外(または別のタイプのチェック済み例外)に変更する正当な理由があります

  1. 未チェックの例外をチェック済みの例外に変換する場合は、どうすればよいですか。

まず、そして最も重要なことは、例外連鎖機能を使用することを確認してください。そうすれば、元の例外からの情報は失われず、デバッグに使用できます。

次に、使用する例外タイプを決定する必要があります。単純な実行時例外を使用すると、呼び出し元が何が問題なのかを判断するのが難しくなりますが、呼び出し元が何が問題なのかを判断しようとする場合、例外を変更してチェックを外してはいけないことを示している可能性があります。


0

ブール型の質問では、2つの論争の的となった回答の後で異なる回答をすることは困難ですが、いくつかの場所で言及したこともありますが、それが持つ重要性については十分に強調されていませんでした。

年月を経て、私は常に誰かが些細な問題について混乱していることに気付きました。

レイヤリング。ソフトウェアアプリケーション(少なくとも、想定されている)レイヤーの山。優れた階層化の重要な期待事項の1つは、下位層が上位層の潜在的に複数のコンポーネントに機能を提供することです。

アプリに、ボトムアップのNET、TCP、HTTP、REST、DATA MODEL、BUSINESSのレイヤーがあるとしましょう。

ビジネスレイヤーが残りの呼び出しを実行する場合は、1秒待ってください。なんでそんなこと言ったの?なぜHTTPリクエストやTCPトランザクションと言ったり、ネットワークパッケージを送信しなかったのですか?それらは私のビジネス層には無関係だからです。私はそれらを扱うつもりはありません、それらの詳細を調べるつもりはありません。私が原因として得た例外に深く関わっていて、それらが存在することさえ知りたくないなら、私は完全に大丈夫です。

さらに、詳細を知っていると悪いです。明日、TCPプロトコルに固有の詳細を扱う下線のトランスポートプロトコルを変更したい場合は、REST抽象化が自分自身を抽象化するのに良い仕事をしなかったためです特定の実装。

例外をレイヤーからレイヤーに移行する場合、そのすべての側面と、現在のレイヤーが提供する抽象化に対してどのような意味を持つかを再検討することが重要です。例外を他のものに置き換えることもあれば、いくつかの例外を組み合わせる場合もあります。また、チェック済みから未チェックに、またはその逆に移行する場合があります。

もちろん、あなたが言及した実際の場所は別の話ですが、一般的には-はい、それは良いことかもしれません。


-2

私の考えでは、

フレームワークレベルでは、実行時例外をキャッチして、同じ場所で呼び出し側へのtryキャッチのブロックを減らす必要があります。

アプリケーションレベルでは、ランタイム例外をキャプチャすることはめったになく、このプラクティスは悪かったと思います。


1
そして、フレームワークレベルでそれらの例外をどうしますか?
マチュー

例外を処理できるUIレイヤーがフレームワークにある場合、UIは何らかのエラーメッセージを表示します。1ページのJavaScriptアプリの場合、アプリはエラーメッセージを表示する可能性があります。確かに、UIレイヤーは、より深いレイヤーが実際にエラーから回復できない場合にのみエラーを処理する必要があります。
ジェイクトロント
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.