Java (またはチェック済みの例外を持つ他の言語) で、独自の例外クラスを作成する場合、チェックするかチェックしないかをどのように決定しますか?
私の本能は、チェックされた例外は、呼び出し元が生産的な方法で回復できる可能性がある場合に呼び出されると言うのですが、チェックされていない例外は回復できない場合に多くなりますが、他の人の考えに興味があります。
Java (またはチェック済みの例外を持つ他の言語) で、独自の例外クラスを作成する場合、チェックするかチェックしないかをどのように決定しますか?
私の本能は、チェックされた例外は、呼び出し元が生産的な方法で回復できる可能性がある場合に呼び出されると言うのですが、チェックされていない例外は回復できない場合に多くなりますが、他の人の考えに興味があります。
Java学習者 より:
例外が発生した場合、例外をキャッチして処理するか、メソッドが例外をスローすることを宣言して例外を処理できないことをコンパイラに伝える必要があります。その場合、メソッドを使用するコードはその例外を処理する必要があります (また、処理できない場合は例外をスローすることを宣言することもできます)。
コンパイラは、2 つのこと (キャッチまたは宣言) のいずれかを行ったことを確認します。したがって、これらはチェック例外と呼ばれます。ただし、エラーとランタイム例外はコンパイラによってチェックされません (キャッチまたは宣言を選択できますが、必須ではありません)。したがって、これら 2 つは非チェック例外と呼ばれます。
エラーは、システムのクラッシュなど、アプリケーションの外部で発生する状況を表すために使用されます。実行時例外は通常、アプリケーション ロジックの障害によって発生します。これらの状況では何もできません。実行時例外が発生すると、プログラム コードを書き直す必要があります。したがって、これらはコンパイラによってチェックされません。これらのランタイム例外は、開発およびテスト期間中に明らかになります。次に、これらのエラーを取り除くためにコードをリファクタリングする必要があります。
多くの層を持つ十分に大きなシステムでは、チェックされた例外は役に立たない.
チェックされた例外を使用すると、エラー処理の戦略が細かく管理され、大規模なシステムでは耐えられなくなります。
ほとんどの場合、API の呼び出し元がどのレイヤーにあるかがわからないため、エラーが「回復可能」かどうかわかりません。
整数の文字列表現を Int に変換する StringToInt API を作成するとします。API が "foo" 文字列で呼び出された場合、チェック例外をスローする必要がありますか? それは回復可能ですか?彼のレイヤーでは、私の StringToInt API の呼び出し元が既に入力を検証している可能性があるため、わかりません。この例外がスローされた場合、それはバグまたはデータの破損であり、このレイヤーでは回復できません。
この場合、API の呼び出し元は例外をキャッチしたくありません。彼は、例外を「バブルアップ」させたいだけです。チェック済み例外を選択した場合、この呼び出し元には、人為的に例外を再スローするためだけに、役に立たない catch ブロックがたくさんあります。
何が回復可能かは、ほとんどの場合、API の作成者ではなく、API の呼び出し元に依存します。チェックされていない例外のみが例外をキャッチするか無視するかを選択できるため、API はチェックされた例外を使用しないでください。
あなたは正しいです。
チェックされていない例外は、システムがすぐに失敗するようにするために使用されます。これは良いことです。適切に機能するためには、メソッドが何を期待しているかを明確に述べる必要があります。このようにして、入力を一度だけ検証できます。
例えば:
/**
* @params operation - The operation to execute.
* @throws IllegalArgumentException if the operation is "exit"
*/
public final void execute( String operation ) {
if( "exit".equals(operation)){
throw new IllegalArgumentException("I told you not to...");
}
this.operation = operation;
.....
}
private void secretCode(){
// we perform the operation.
// at this point the opreation was validated already.
// so we don't worry that operation is "exit"
.....
}
例を挙げるだけです。要点は、システムがすぐに失敗した場合、どこで、なぜ失敗したかを知ることができるということです。次のようなスタックトレースが得られます。
IllegalArgumentException: I told you not to use "exit"
at some.package.AClass.execute(Aclass.java:5)
at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569)
ar ......
そして、あなたは何が起こったのかを知るでしょう。"delegateTheWork" メソッド (4569 行目) の OtherClass は、「exit」値を使用してクラスを呼び出しました。
そうしないと、コード全体に検証を振りかける必要があり、エラーが発生しやすくなります。さらに、何が問題なのかを追跡するのが難しい場合があり、イライラするデバッグに何時間もかかることが予想される場合があります。
NullPointerExceptions でも同じことが起こります。約 15 のメソッドを持つ 700 行のクラスがあり、30 の属性を使用し、どれも null にすることができない場合、これらのメソッドのそれぞれで null 可能性を検証する代わりに、これらのすべての属性を読み取り専用にして、コンストラクターで検証するか、工場方式。
public static MyClass createInstane( Object data1, Object data2 /* etc */ ){
if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); }
}
// the rest of the methods don't validate data1 anymore.
public void method1(){ // don't worry, nothing is null
....
}
public void method2(){ // don't worry, nothing is null
....
}
public void method3(){ // don't worry, nothing is null
....
}
チェックされた例外は、プログラマー (あなたまたはあなたの同僚) がすべてを正しく行い、入力を検証し、テストを実行し、すべてのコードが完璧であるが、コードがダウンしている可能性のあるサード パーティの Web サービス (またはファイル) に接続する場合に役立ちます。使用していたものが別の外部プロセスによって削除されたなど)。接続が試行される前に Web サービスが検証される場合もありますが、データ転送中に問題が発生しました。
そのシナリオでは、あなたやあなたの同僚がそれを助けるためにできることは何もありません. しかし、それでも何かをしなければならず、アプリケーションがただ死んでユーザーの目に消えてしまわないようにする必要があります。そのためにチェック済み例外を使用し、例外を処理します。それが発生したときに何ができますか?、ほとんどの場合、エラーをログに記録しようとするだけで、おそらく作業 (アプリの作業) を保存し、ユーザーにメッセージを表示します。 . (サイト blabla がダウンしています。後で再試行してくださいなど)
チェックされた例外が (すべてのメソッド シグネチャに「throw Exception」を追加することにより) 過剰に使用されると、コードは非常に壊れやすくなります。これは、誰もがその例外を無視し (一般的すぎるため)、コードの品質が深刻になるためです。妥協した。
非チェック例外を使いすぎると、同様のことが起こります。そのコードのユーザーは、多くの try{...}catch( Throwable t ) が表示されるため、何か問題が発生する可能性があるかどうかわかりません。
これが私の「最終的な経験則」です。
私が使う:
前の回答と比較すると、これは、どちらか一方 (または両方) の種類の例外を使用するための明確な根拠 (同意または反対することができます) です。
これらの例外の両方について、非常に一般的な未チェックの例外 (NullPointerException など) を除いて、自分のアプリケーション用に独自の未チェックおよびチェック済みの例外を作成します (ここで説明したように、良い習慣です)。
たとえば、以下の特定の関数の目的は、オブジェクトを作成する (または既に存在する場合は取得する) ことです。つまり、次のことを
意味します。
例:
/**
* Build a folder. <br />
* Folder located under a Parent Folder (either RootFolder or an existing Folder)
* @param aFolderName name of folder
* @param aPVob project vob containing folder (MUST NOT BE NULL)
* @param aParent parent folder containing folder
* (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob)
* @param aComment comment for folder (MUST NOT BE NULL)
* @return a new folder or an existing one
* @throws CCException if any problems occurs during folder creation
* @throws AssertionFailedException if aParent is not in the same PVob
* @throws NullPointerException if aPVob or aParent or aComment is null
*/
static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent,
final IPVob aPVob, final Comment aComment) throws CCException {
Folder aFolderRes = null;
if (aPVob.equals(aParent.getPVob() == false) {
// UNCHECKED EXCEPTION because the caller failed to live up
// to the documented entry criteria for this function
Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); }
final String ctcmd = "mkfolder " + aComment.getCommentOption() +
" -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName);
final Status st = getCleartool().executeCmd(ctcmd);
if (st.status || StringUtils.strictContains(st.message,"already exists.")) {
aFolderRes = Folder.getFolder(aFolderName, aPVob);
}
else {
// CHECKED EXCEPTION because the callee failed to respect his contract
throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'");
}
return aFolderRes;
}
例外から回復する能力だけの問題ではありません。私の意見では、最も重要なことは、呼び出し元が例外をキャッチすることに関心があるかどうかです。
他の場所で使用するライブラリ、またはアプリケーションの下位レベルのレイヤーを作成する場合は、呼び出し元が例外をキャッチする (知る) ことに関心があるかどうかを自問してください。そうでない場合は、未チェックの例外を使用して、不必要に彼に負担をかけないようにします。
これは、多くのフレームワークで使用されている哲学です。特に、Spring と hibernate が思い浮かびます。Java ではチェック済み例外が過剰に使用されているため、これらは既知のチェック済み例外を未チェック例外に正確に変換します。私が考えることができる 1 つの例は、json.org からの JSONException です。これは、チェックされた例外であり、ほとんど迷惑です。
ところで、ほとんどの場合、例外に対する呼び出し元の関心は、例外から回復する能力に直接関係していますが、常にそうであるとは限りません。
これは、チェック済み/未チェックのジレンマに対する非常に簡単な解決策です。
ルール 1: 未チェックの例外は、コードが実行される前のテスト可能な状態と考えてください。たとえば…</p>
x.doSomething(); // the code throws a NullPointerException
ここで、x は null です... …コードには次のようなものがあるはずです…</p>
if (x==null)
{
//do something below to make sure when x.doSomething() is executed, it won’t throw a NullPointerException.
x = new X();
}
x.doSomething();
ルール 2: チェック例外は、コードの実行中に発生する可能性のあるテスト不可能な状態と考えてください。
Socket s = new Socket(“google.com”, 80);
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
…上記の例では、DNS サーバーがダウンしているため、URL (google.com) を使用できない可能性があります。DNS サーバーが動作して「google.com」の名前を IP アドレスに解決した瞬間でも、google.com に接続すると、後でいつでもネットワークがダウンする可能性があります。ストリームを読み書きする前に、常にネットワークをテストすることはできません。
問題があるかどうかを知る前に、コードを実行する必要がある場合があります。開発者に、Checked Exception を介してこれらの状況を処理するよう強制するような方法でコードを作成するように強制することで、この概念を発明した Java の作成者に敬意を表する必要があります。
一般に、Java のほとんどすべての API は、上記の 2 つの規則に従います。ファイルに書き込もうとすると、書き込みが完了する前にディスクがいっぱいになる可能性があります。他のプロセスが原因でディスクがいっぱいになった可能性があります。この状況をテストする方法はまったくありません。ハードウェアの使用がいつでも失敗する可能性があるハードウェアとやり取りする人にとって、チェック例外はこの問題に対する洗練された解決策のようです。
これにはグレーゾーンがあります。多くのテストが必要な場合 (大量の && と || を含む if ステートメントに圧倒されます)、スローされる例外は CheckedException になります。これは、正しく処理するのが面倒すぎるためです。この問題を単純に言うことはできません。プログラミングエラーです。テストの数が 10 よりはるかに少ない場合 (「if (x == null)」など)、プログラマ エラーは UncheckedException になります。
言語通訳者を扱うとき、物事は面白くなります。上記のルールに従って、構文エラーはチェック済みまたは未チェックの例外と見なされますか? 言語の構文を実行前にテストできるのであれば、それは UncheckedException であるべきだと私は主張します。言語をテストできない場合 (パーソナル コンピューターでアセンブリ コードを実行する場合と同様)、構文エラーはチェック済み例外になります。
上記の 2 つのルールにより、どちらを選択するかについての懸念の 90% が取り除かれます。ルールを要約すると、次のパターンに従います… 1) 実行するコードが正しく実行されるように実行前にテストでき、例外が発生した場合 — 別名プログラマー エラーの場合、例外は UncheckedException (RuntimeException のサブクラス) である必要があります。 )。2) 実行されるコードが正しく実行されるために実行前にテストできない場合、例外は Checked Exception (Exception のサブクラス) である必要があります。
これは、チェックされた例外またはチェックされていない例外と呼ぶことができます。ただし、どちらのタイプの例外もプログラマーがキャッチできるため、最善の答えは、すべての例外を未チェックとして記述し、文書化することです。そうすれば、API を使用する開発者は、その例外をキャッチして何かを実行するかどうかを選択できます。チェック例外は、すべての人の時間の完全な無駄であり、コードを見ると衝撃的な悪夢になります。適切な単体テストにより、キャッチして何かを行う必要がある例外が表示されます。
チェック された例外: クライアントが例外から回復でき、続行したい場合は、チェックされた例外を使用します。
未チェックの例外: クライアントが例外の後に何もできない場合、未チェックの例外を発生させます。
例: メソッド A() で算術演算を行うことが期待され、A() からの出力に基づいている場合は、別の演算を行う必要があります。実行時に予期しないメソッド A() からの出力が null である場合、実行時例外である Null ポインター例外をスローすることが期待されます。
ここを参照
特にAPIを設計するときは、原則として未チェックの例外を好むことに同意します。呼び出し元は、文書化された未チェックの例外をキャッチすることをいつでも選択できます。発信者に不必要に強制するだけではありません。
チェック例外は、実装の詳細として、下位レベルで役立つと思います。特定のエラー「リターンコード」を管理するよりも、制御メカニズムのフローが優れているように思われることがよくあります。低レベルのコード変更のアイデアの影響を確認するのにも役立つ場合があります...チェック済み例外をダウンストリームで宣言し、誰が調整する必要があるかを確認します。この最後のポイントは、汎用の catch(Exception e) またはthrows Exception が多数ある場合には当てはまりません。どちらにせよ、通常はあまりよく考えられていません。
チェック例外は、呼び出し元に情報を提供したい回復可能なケース (権限が不十分、ファイルが見つからないなど) に役立ちます。
非チェック例外は、ユーザーまたはプログラマーに実行時の重大なエラーまたは予期しない状態を通知するために、たとえあったとしてもめったに使用されません。他の人が使用するコードまたはライブラリを作成している場合は、それらをスローしないでください。コンパイラはそれらをキャッチまたは宣言することを強制しないため、ソフトウェアが未チェックの例外をスローすることを期待していない可能性があるためです。
いくつかの質問からの例外について考えることができると思います:
なぜ例外が発生するのですか?それが起こったときに私たちができること
うっかり、バグ。null オブジェクトのメソッドが呼び出されるなど。
String name = null;
... // some logics
System.out.print(name.length()); // name is still null here
この種の例外は、テスト中に修正する必要があります。そうしないと、生産が中断され、すぐに修正する必要がある非常に重大なバグが発生します。この種の例外はチェックする必要はありません。
外部からの入力によって、外部サービスの出力を制御または信頼することはできません。
String name = ExternalService.getName(); // return null
System.out.print(name.length()); // name is null here
ここで、名前が null のときに続行する場合は、名前が null であるかどうかを確認する必要がある場合があります。そうでない場合は、そのままにしておくと、ここで停止し、呼び出し元にランタイム例外が発生します。この種の例外はチェックする必要はありません。
外部からの実行時例外により、外部サービスを制御または信頼することはできません。
ここで、ExternalService が発生したときに続行する場合は、ExternalService からのすべての例外をキャッチする必要がある場合があります。それ以外の場合は、放っておけば、ここで停止し、呼び出し元に実行時例外が発生します。
外部からのチェック例外により、外部サービスを制御または信頼することはできません。
ここで、ExternalService が発生したときに続行する場合は、ExternalService からのすべての例外をキャッチする必要がある場合があります。それ以外の場合は、放っておけば、ここで停止し、呼び出し元に実行時例外が発生します。
この場合、ExternalService で発生した例外の種類を知る必要がありますか? 場合によります:
ある種の例外を処理できる場合は、それらをキャッチして処理する必要があります。他の人のために、それらを泡立ててください。
特定の実行のログまたはユーザーへの応答が必要な場合は、それらをキャッチできます。他の人のために、それらを泡立ててください。
例外が予想される可能性が低く、それをキャッチした後でも続行でき、その例外を回避するために何もできない場合はいつでも、チェック済み例外を使用できます。
特定の例外が発生したときに何か意味のあることをしたいとき、およびその例外が予想されるが確実ではないときにいつでも、チェック済み例外を使用できます。
例外が異なるレイヤーをナビゲートするたびに、すべてのレイヤーでそれをキャッチする必要はありません。その場合、実行時例外を使用するか、例外を未チェックの例外としてラップできます。
実行時例外は、例外が発生する可能性が最も高く、先に進む方法がなく、何も回復できない場合に使用されます。したがって、この場合、その例外に関して予防策を講じることができます。例: NUllPointerException、ArrayOutofBoundsException。これらは発生する可能性が高くなります。このシナリオでは、コーディング中に予防策を講じて、このような例外を回避できます。そうしないと、try catch ブロックをあらゆる場所に記述する必要があります。
より一般的な例外は未チェックにすることができ、一般的でない例外はチェックされます。
Application Exception を宣言するときは、Unchecked Exception、つまり RuntimeException のサブクラスにする必要があると思います。その理由は、メソッドの try-catch と throws 宣言でアプリケーション コードが乱雑にならないようにするためです。アプリケーションがJava Apiを使用している場合、チェックされた例外をスローしますが、とにかく処理する必要があります。それ以外の場合、アプリケーションは未チェックの例外をスローできます。アプリケーションの呼び出し元がまだチェックされていない例外を処理する必要がある場合は、それを行うことができます。
私が使用するルールは、未チェックの例外を使用しないことです。(または、それを回避する方法が表示されない場合)
ライブラリを使用する開発者、またはライブラリ/アプリケーションを使用するエンドユーザーの観点からすると、予期せぬ例外が原因でクラッシュするアプリケーションに直面するのは本当に嫌なことです。そして、キャッチオールに期待するのも良くありません。
このようにして、アプリケーションが完全に消えるのではなく、エンド ユーザーにエラー メッセージを表示することができます。