回答:
チェック例外は、いつ使用すべきかを理解している限り、すばらしいものです。JavaコアAPIは、SQLException(および場合によってはIOException)に関するこれらのルールに準拠していないため、非常にひどいです。
チェックされた例外は、予測可能であるが回避できないエラーから回復するために使用する必要があります。
未チェックの例外は他のすべてに使用する必要があります。
ほとんどの人がこれが何を意味するのか誤解しているので、私はあなたのためにこれを分解します。
スローしている例外が上記の条件をすべて満たしていない限り、チェックされていない例外を使用する必要があります。
すべてのレベルで再評価:チェックされた例外をキャッチするメソッドが、エラーを処理するのに適していない場合があります。その場合は、自分の発信者にとって何が妥当かを検討してください。例外が予測可能で、防止できず、回復するのに合理的である場合は、チェックされた例外を自分でスローする必要があります。そうでない場合は、チェックされていない例外で例外をラップする必要があります。このルールに従うと、どのレイヤーにいるかによって、チェックされた例外をチェックされていない例外に、またはその逆に変換します。
チェックされた例外とチェックされていない例外の両方について、適切な抽象化レベルを使用します。たとえば、2つの異なる実装(データベースとファイルシステム)を備えたコードリポジトリでは、SQLException
またはをスローすることで実装固有の詳細を公開しないようにする必要がありますIOException
。代わりに、すべての実装にまたがる抽象化で例外をラップする必要があります(例:)RepositoryException
。
Java学習者から:
例外が発生した場合は、例外をキャッチして処理するか、メソッドがその例外をスローすることを宣言して処理できないことをコンパイラに通知する必要があります。そうすると、メソッドを使用するコードはその例外を処理する必要があります(例外でも)処理できない場合は例外をスローすることを宣言することもできます)。
コンパイラーは、2つの処理(キャッチまたは宣言)のいずれかを実行したことを確認します。したがって、これらはチェック済み例外と呼ばれます。ただし、エラーおよびランタイム例外はコンパイラーによってチェックされません(キャッチまたは宣言することを選択できても、必須ではありません)。したがって、これら2つはチェックされない例外と呼ばれます。
エラーは、システムのクラッシュなど、アプリケーションの外部で発生する状態を表すために使用されます。ランタイム例外は通常、アプリケーションロジックの障害によって発生します。このような状況では何もできません。実行時例外が発生した場合は、プログラムコードを書き直す必要があります。したがって、これらはコンパイラーによってチェックされません。これらのランタイム例外は、開発およびテスト期間中に明らかになります。次に、これらのエラーを削除するためにコードをリファクタリングする必要があります。
私が使用するルールは、チェックされていない例外を使用しないことです。(またはそれを回避する方法がない場合)
反対の場合には非常に強力なケースがあります。チェックされた例外を使用しないでください。私はこの議論に賛成するのをためらいますが、チェックされた例外を導入することは、後から見た誤った決定であるという幅広いコンセンサスがあるようです。メッセンジャーを撃って、それらの 議論を参照しないでください。
foo
スローbarException
するようにドキュメント化されており、それを予期していないにもかかわらずfoo
スローするメソッドを呼び出す場合、呼び出すコードは、ファイルの終わりに到達したと見なし、予期しないことが起こったという手がかりはありません。私はその状況を、チェックされた例外が最も役立つはずであると考えていますが、コンパイラが未処理のチェックされた例外を許可する唯一のケースでもあります。barException
foo
foo
多くの層を持つ十分な規模のシステムでは、チェックされた例外は役に立たない。とにかく、例外の処理方法を処理するためのアーキテクチャレベルの戦略が必要である(障害バリアを使用)。
チェックされた例外を除いて、エラー処理ステートはマイクロマネージドであり、大規模なシステムでは耐えられません。
ほとんどの場合、APIの呼び出し元がどの層にあるのかわからないため、エラーが「回復可能」かどうかはわかりません。
整数の文字列表現をIntに変換するStringToInt APIを作成するとします。APIが「foo」文字列で呼び出された場合、チェック例外をスローする必要がありますか?回復可能ですか?彼のレイヤーでは、私のStringToInt APIの呼び出し元が既に入力を検証している可能性があるため、私にはわかりません。この例外がスローされた場合、それはバグまたはデータの破損であり、このレイヤーでは回復できません。
この場合、APIの呼び出し元は例外をキャッチしたくありません。彼は例外を「バブルアップ」させたいだけです。チェックされた例外を選択した場合、この呼び出し元は、人為的に例外を再スローするためだけに、多くの役に立たないcatchブロックを持っています。
回復可能なものは、ほとんどの場合、APIの作成者ではなく、APIの呼び出し元に依存します。チェックされていない例外のみが例外をキャッチするか無視するかを選択できるため、APIはチェックされた例外を使用しないでください。
あなたは正しいです。
チェックされていない例外を使用して、システムをすばやく失敗させることができます。これは良いことです。メソッドが適切に機能するためには、メソッドが何を期待しているのかを明確に示す必要があります。この方法では、入力を1回だけ検証できます。
例えば:
/**
* @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:コードが実行される前に、チェックされない例外をテスト可能な条件と考えます。例えば…
x.doSomething(); // the code throws a NullPointerException
ここで、xはnullです...…コードには次のものが含まれている可能性があります…
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)実行するコードが正しく実行されるために実行前にテストできない場合、例外はチェック済み例外(Exceptionのサブクラス)である必要があります。
これをチェック済みまたは未チェックの例外と呼ぶことができます。ただし、どちらのタイプの例外もプログラマーがキャッチできるため、最善の答えは、すべての例外をチェックされていないものとして記述し、それらを文書化することです。これにより、APIを使用する開発者は、その例外をキャッチして何かを実行するかどうかを選択できます。チェックされた例外は、すべての人の時間を完全に浪費するものであり、コードを見ると衝撃的な悪夢になります。適切な単体テストを行うと、キャッチして何かをしなければならない可能性のある例外が表示されます。
特にAPIを設計する場合は、原則として未チェックの例外を優先することに同意します。呼び出し側は常に、文書化された未チェックの例外をキャッチすることを選択できます。不必要に発信者を強制するだけではありません。
実装の詳細として、チェック例外は下位レベルで役立ちます。指定されたエラー「戻りコード」を管理する必要があるよりも、制御メカニズムのフローが優れているように見えることがよくあります。低レベルのコード変更のアイデアの影響を確認するのにも役立つ場合があります...チェックされた例外を下流で宣言し、誰が調整する必要があるかを確認します。この最後の点は、多くのジェネリックがある場合には当てはまりません 。catch(Exception e) または例外 をスローしますが、通常はとにかくあまり考えられていません。
ここでは、長年の開発経験の後に私が持っている私の意見を共有したいと思います。
例外を確認しました。これは、ビジネスユースケースまたはコールフローの一部です。これは、私たちが期待する、または期待しないアプリケーションロジックの一部です。たとえば、接続が拒否された、条件が満たされていないなどです。これを処理し、対応するメッセージをユーザーに表示して、何が起こったか、次に何をすべきかを指示します(後で再試行するなど)。私は通常、これを後処理例外または「ユーザー」例外と呼びます。
未チェックの例外。これはプログラミング例外の一部であり、ソフトウェアコードプログラミングのいくつかの間違い(バグ、欠陥)であり、ドキュメントに従ってプログラマーがAPIを使用する方法を反映しています。外部のlib / frameworkドキュメントが、NPEまたはIllegalArgumentExceptionがスローされるため、ある範囲のデータとnull以外のデータを取得すると想定している場合、プログラマーはそれを予期し、ドキュメントに従ってAPIを正しく使用する必要があります。それ以外の場合は、例外がスローされます。私は通常、これを前処理例外または「検証」例外と呼びます。
対象者ごと。ここで、例外が設計された対象読者または人々のグループについて話しましょう(私の意見に従って):
アプリケーション開発ライフサイクルフェーズごと。
フレームワークが通常チェックされない例外(Springなど)を使用する理由は、フレームワークがアプリケーションのビジネスロジックを決定できないためです。これは、開発者が独自のロジックをキャッチして設計することです。
プログラマーのエラーかどうかに基づいて、これら2つのタイプの例外を区別する必要があります。
FileNotFoundExceptionは、微妙な違いを理解する良い例です。FileNotFoundExceptionは、ファイルが見つからない場合にスローされます。この例外には2つの理由があります。ファイルパスが開発者によって定義されているか、GUIを介してエンドユーザーから取得されている場合、チェックされない例外になります。ファイルが他の誰かによって削除された場合、それはチェック例外です。
チェック例外は2つの方法で処理できます。これらは、try-catchを使用しているか、例外を伝播しています。例外の伝播の場合、例外処理のため、コールスタック内のすべてのメソッドが密結合されます。そのため、チェック例外を注意深く使用する必要があります。
レイヤードエンタープライズシステムを開発する場合、ほとんどチェックされていない例外をスローするように選択する必要がありますが、何もできない場合はチェックされた例外を使用することを忘れないでください。
チェックされた例外は、呼び出し元に情報を提供する必要がある回復可能な場合(つまり、不十分なアクセス許可、ファイルが見つからないなど)に役立ちます。
未チェックの例外は、実行時にユーザーまたはプログラマーに重大なエラーや予期しない状態を通知するために使用されることはほとんどありません。他の人が使用するコードやライブラリを作成している場合は、コンパイラがそれらを強制的にキャッチまたは宣言しないため、ソフトウェアが未チェックの例外をスローすることを期待していない可能性があるため、それらをスローしないでください。
例外が予想される可能性は低く、それをキャッチした後でも処理を続行でき、その例外を回避するために何もできない場合は、チェック済み例外を使用できます。
特定の例外が発生したときに意味のあることを行いたい場合、およびその例外が予期されているが確実ではない場合は、チェック例外を使用できます。
例外が異なるレイヤーで移動するときは常に、すべてのレイヤーでキャッチする必要はありません。その場合、ランタイム例外またはラップ例外を未チェックの例外として使用できます。
ランタイム例外は、例外が発生する可能性が最も高い場合に使用されます。これ以上進む方法はなく、回復可能なものはありません。したがって、この場合、その例外に関して予防策を講じることができます。EX:NUllPointerException、ArrayOutofBoundsException。これらが発生する可能性が高くなります。このシナリオでは、そのような例外を回避するためにコーディング中に予防策を講じることができます。それ以外の場合は、すべての場所でtry catchブロックを記述する必要があります。
より一般的な例外はチェックなしにでき、一般的でないものはチェックされます。
私はいくつかの質問から例外を考えることができると思います:
なぜ例外が発生するのですか?それが起こったときに何ができるか
誤って、バグ。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で発生した例外の種類を知る必要がありますか?場合によります:
ある種の例外を処理できる場合は、それらをキャッチして処理する必要があります。他の人のために、それらを泡立たせます。
ユーザーに特定の実行に関するログまたは応答が必要な場合は、それらをキャッチできます。他の人のために、それらを泡立たせます。
アプリケーション例外を宣言するときは、チェックされていない例外、つまりRuntimeExceptionのサブクラスである必要があると思います。その理由は、try-catchを使用してアプリケーションコードを乱雑にせず、メソッドに宣言をスローするためです。アプリケーションがJava Apiを使用していて、処理する必要があるチェック例外をスローする場合。その他の場合、アプリケーションは未チェックの例外をスローできます。アプリケーションの呼び出し元が未チェックの例外を処理する必要がある場合は、それを行うことができます。
私が使用するルールは、チェックされていない例外を使用しないことです。(またはそれを回避する方法がない場合)
ライブラリを使用している開発者またはライブラリ/アプリケーションを使用しているエンドユーザーの視点から見ると、未熟な例外のためにクラッシュするアプリケーションに直面するのは本当にうんざりです。キャッチオールを当てにするのもよくありません。
これにより、アプリケーションが完全に消えるのではなく、エンドユーザーにエラーメッセージが表示されます。