同僚のコードを読んだところ、彼がさまざまな例外を頻繁にキャッチし、代わりに常に「RuntimeException」をスローすることがわかりました。これは非常に悪い習慣だといつも思っていました。私が間違っている?
私はあなたの同僚が何かを間違っているかどうかを知るのに十分な状況を知りません、それで私はこれを一般的な意味で議論します。
チェックされた例外をある種の実行時例外に変換することは常に間違った習慣だとは思いません。 チェックされた例外は、開発者によるしばしば誤用および乱用です。
チェックされた例外を使用するつもりがない場合(回復不可能な状態、または制御フローさえ)は、非常に簡単に使用できます。特に、チェックされた例外が、呼び出し元が回復できない状況で使用されている場合、その例外を、有用なメッセージ/状態を持つランタイム例外に変えることは正当化されると思います。残念ながら、回復不可能な状態に直面している多くの場合、彼らはあなたができる最悪のことの一つである空のキャッチブロックを持っている傾向があります。このような問題のデバッグは、開発者が遭遇する可能性のある最大の問題の1つです。
したがって、回復可能な状態を処理していると思われる場合は、それに応じて処理する必要があり、例外を実行時例外にしないでください。 チェックされた例外が回復不可能な条件で使用される場合、それを実行時例外に変換することは正当化されます。
それはGOODにすることができます。お読みください このonjava.comの記事 :
ほとんどの場合、クライアントコードはSQLExceptionについて何も実行できません。それらを未チェックの例外に変換することをためらわないでください。次のコードを考えてみましょう。
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 を参照してください
最近、Bruce EckelやRod Johnsonを含むいくつかの評判の高い専門家は、チェック例外に関する正統な立場に最初は完全に同意したものの、チェック例外の排他的使用はそれよりも良いアイデアではないと結論付けたと公に述べています最初に表示され、そのチェックされた例外は多くの大規模プロジェクトの重大な問題の原因となっています。 Eckelはより極端な見方をしており、すべての例外をオフにする必要があることを示唆しています。ジョンソンの見方はより保守的ですが、チェックされた例外に対するオーソドックスな好みは過度であることを依然として示唆しています。 (Javaテクノロジーの使用経験がほぼ確実であるC#のアーキテクトが、チェック済みの例外を言語設計から除外することを選択し、すべての例外を未チェックの例外にしました。ただし、 、後でチェック例外の実装のための余地を残してください。)
また、同じリンクから:
未チェックの例外を使用するという決定は複雑なものであり、明確な答えがないことは明らかです。 Sunのアドバイスはそれらを無料で使用することであり、C#のアプローチ(Eckelと他の人は同意する)はそれらをすべてに使用することです。他の人は、「中立の立場がある」と言います。
いいえ、あなたは間違っていません。彼の実践は非常に間違っています。それを引き起こした問題をキャプチャする例外をスローする必要があります。 RunTimeExceptionは広範囲であり、到達しすぎます。 NullPointerException、ArgumentExceptionなどである必要があります。何が問題だったかを正確に説明しています。これにより、処理する必要のある問題を区別して、プログラムを存続させることができます。また、「合格しない」シナリオである必要があるエラーと区別することができます。質問で提供された情報に欠けているものがない限り、彼がやっていることは、「On Error Resume Next」より少しだけ優れています。
場合によります。
このやり方は賢いかもしれません。多くの状況(たとえば、Web開発など)があり、何らかの例外が発生した場合、何も実行できません(たとえば、コードから矛盾するDBを修復できないため:-)。開発者しか実行できません)。これらの状況では、スローされた例外をランタイム例外にラップしてから再スローするのが賢明です。いくつかの例外処理レイヤーでこれらすべての例外をキャッチし、エラーをログに記録して、ユーザーに素敵なローカライズされたエラーコード+メッセージを表示できます。
一方で、例外がランタイムではない(チェックされている)場合、APIの開発者は、この例外は解決可能であり、修復する必要があることを示します。可能であれば、間違いなくそれを行うべきです。
他の解決策は、このチェックされた例外を呼び出しレイヤーに再スローすることですが、例外が発生した場所でそれを解決できない場合は、ここでも解決できない可能性があります...
これについてコメントをお願いしたいのですが、これが必ずしも悪い習慣ではないことがあります。 (またはひどく悪い)。しかし、多分私は間違っています。
多くの場合、使用しているAPIは、特定のユースケースで実際にスローされるとは想像できない例外をスローします。この場合、キャッチされた例外を原因としてRuntimeExceptionをスローすることはまったく問題ありません。この例外がスローされた場合、それはおそらくプログラミングエラーの原因であり、正しい仕様の範囲内ではありません。
RuntimeExceptionが後でキャッチおよび無視されないと仮定すると、OnErrorResumeNextの近くにはありません。
OnErrorResumeNextは、誰かが例外をキャッチして、単にそれを無視するか、単に出力するときに発生します。これは、ほとんどすべての場合にひどく悪い習慣です。
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);
}
}
これは、チェックされた例外を処理する正しい方法ではありません。 thisメソッドのスコープで例外を処理するだけの能力がないということは、アプリがクラッシュする必要があることを意味しません。代わりに、次のように上位のスコープに伝達するのが適切です。
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で除算しない場合、例外が発生することはなく、例外が発生することはないため、その例外のケースを処理する必要はありません。ただし、ファイルにアクセスする場合、一度成功しても、次に成功するわけではありません。ユーザーが権限を変更した可能性があり、別のプロセスがファイルを削除または変更した可能性があります。したがって、常に例外的なケースを処理する必要があります。そうしないと、バグが発生する可能性があります。
これは、多くのフレームワークで一般的な方法です。例えば。 Hibernate
はまさにこれを行います。 APIを呼び出す場所でAPIを処理するためのコードを明示的に記述する必要があるため、APIはクライアント側に侵入するべきではなく、Exception
は侵入する必要があるという考えです。しかし、その場所は、そもそも例外を処理するのに適切な場所ではない可能性があります。
正直に言うと、これは「ホット」なトピックであり、多くの論争があるので、私は脇に立ちませんが、あなたの友人がすること/提案することは、異常または珍しいことではないと言います。
ブール式の質問では、2つの論争の的となる回答の後で別の答えを出すのは困難ですが、いくつかの場所で言及されていても、その重要性に対して十分に強調されていなかったという見方を与えたいと思います。
長年の間に、些細な問題について誰かが常に混乱していることに気付きました。彼らはいくつかの基本的なことを理解していません。
レイヤー化。ソフトウェアアプリケーション(少なくとも想定される)が積み重なったレイヤーの山。良好な階層化のための1つの重要な期待は、下位層が上位層からの潜在的に複数のコンポーネントに機能を提供することです。
アプリに、NET、TCP、HTTP、REST、DATA MODEL、BUSINESSというボトムアップのレイヤーがあるとします。
ビジネスレイヤーが休憩呼び出しを実行する場合は、...しばらくお待ちください。なぜ私はそれを言ったのですか? HTTPリクエストまたはTCPトランザクションまたはネットワークパッケージの送信を言わなかったのはなぜですか?これらは私のビジネス層には関係がないためです。それらを処理するつもりはないので、詳細を調べません。それらが原因として私が得る例外に深く関わっていて、それらが存在することさえ知りたくなければ、私は完全に大丈夫です。
さらに、詳細を知っているのは悪いことです。明日、TCPプロトコルに固有の詳細を処理する下線を引くトランスポートプロトコルを変更したい場合、REST抽象化は、特定の実装からそれ自体を抽象化するための良い仕事をしませんでした。
例外を層から層へと移行するときは、例外のすべての側面と、現在の層が提供する抽象化に対してどのような意味があるかを再検討することが重要です。例外を他のものに置き換えることかもしれません、それはいくつかの例外を組み合わせるかもしれません。また、チェック済みから未チェックに、またはその逆に移行する場合もあります。
もちろん、あなたが言及した実際の場所は別の話ですが、一般的には-はい、それは良いことかもしれません。
「チェックされた例外」全体は悪い考えです。
構造化プログラミングでは、情報が関数間(またはJava用語、メソッド)で「近く」にある場合にのみ渡されます。より正確には、情報は関数間で2つの方法でのみ移動できます。
引数の受け渡しを介して、呼び出し元から呼び出し先へ。
戻り値として、呼び出し先から呼び出し元へ。
これは根本的に良いことです。これにより、コードをローカルで推論することができます。プログラムの一部を理解または変更する必要がある場合は、その部分と他の「近くの」部分だけを調べる必要があります。
ただし、状況によっては、情報を「離れた」機能に送信する必要があります。これは、例外を使用する必要がある場合です。例外はsecretraiser(コードのどの部分にもthrow
ステートメントが含まれている可能性がある)からhandlerに送信されるメッセージです。 =(コードのどの部分にも、catch
nであった例外と互換性のあるthrow
ブロックが含まれている可能性があります)。
チェックされた例外はメカニズムの秘密を破壊し、それとともに、その存在のまさにその理由を破壊します。関数が呼び出し元に情報を「知って」もらう余裕がある場合は、その情報を戻り値の一部として直接送信します。
これはケースバイケースで決まる場合があります。特定のシナリオでは、友人がしていることを実行するのが賢明です。たとえば、一部のクライアントのAPIを公開していて、特定の実装の例外が特定のものであることがわかっている実装の詳細をクライアントに最小限に認識させたい場合などです。実装の詳細であり、クライアントに公開することはできません。
チェックされた例外を邪魔にならないようにすることで、クライアント自体が例外条件を事前検証している可能性があるため、クライアントがよりクリーンなコードを記述できるようにするAPIを公開できます。
たとえば、Integer.parseInt(String)は文字列を受け取り、それに対応する整数を返し、文字列が数値でない場合はNumberFormatExceptionをスローします。ここで、フィールドage
を使用したフォーム送信がこのメソッドによって変換されることを想像してください。ただし、クライアントはすでに検証を保証しているため、例外のチェックを強制する意味はありません。
ここにはいくつか質問があります
一般的な経験則では、呼び出し元がキャッチして回復することが期待される例外をチェックする必要があります。その他の例外(操作全体を中止することが唯一の合理的な結果である場合、またはそれらを特に処理することを心配する価値がないほど十分に考えられない場合)のチェックをはずす必要があります。
例外がキャッチとリカバリに値するかどうかの判断が、使用しているAPIの判断と異なる場合があります。状況が重要な場合があります。ある状況で処理する価値がある例外は、別の状況では処理する価値がない場合があります。時々あなたの手は既存のインターフェースによって強制されます。したがって、はい、チェックされた例外をチェックされていない例外(または別の種類のチェックされた例外)にする正当な理由があります。
まず、そして最も重要なことは、例外チェーン機能を使用することを確認してください。これにより、元の例外からの情報が失われることなく、デバッグに使用できます。
次に、使用する例外タイプを決定する必要があります。単純なruntimeexceptionを使用すると、呼び出し元が問題の原因を特定するのが難しくなりますが、呼び出し元が問題の原因を特定しようとしている場合は、例外を変更してチェックを解除してはいけないことを示している可能性があります。