502

何年もの間、私は次の質問に対するまともな答えを得ることができませんでした: なぜ一部の開発者はチェック例外に反対するのでしょうか? 私は数多くの会話をしたり、ブログを読んだり、Bruce Eckel の発言を読んだりしました (彼らに反対する人を初めて見ました)。

現在、いくつかの新しいコードを作成しており、例外の処理方法に細心の注意を払っています。私は「私たちはチェックされた例外が好きではない」群衆の視点を見ようとしていますが、まだそれを見ることができません.

私が行ったすべての会話は、同じ質問に答えられずに終わります...設定させてください:

一般に (Java の設計方法から)、

  • Error決して捕まえてはいけないもの用です(VMにはピーナッツアレルギーがあり、誰かがピーナッツの瓶を落としました)
  • RuntimeExceptionプログラマーが間違ったこと (プログラマーが配列の最後を歩いた) のためのものです。
  • Exception(例外RuntimeException) は、プログラマーの管理外のものです (ファイル システムへの書き込み中にディスクがいっぱいになり、プロセスのファイル ハンドルの制限に達し、それ以上ファイルを開くことができなくなります)。
  • Throwableすべての例外タイプの単なる親です。

私がよく耳にする議論は、例外が発生した場合、開発者はプログラムを終了するだけだというものです。

私がよく耳にするもう 1 つの議論は、チェック済み例外によってコードのリファクタリングが難しくなるというものです。

「終了するだけです」という引数については、終了する場合でも、適切なエラー メッセージを表示する必要があると述べています。エラーを処理するだけなら、理由を明確に示さずにプログラムが終了しても、ユーザーはあまり満足しません。

「リファクタリングが難しくなる」群集にとって、これは適切なレベルの抽象化が選択されていないことを示しています。メソッドが をスローすることを宣言するのではなく、何が起こっているのかにより適した例外に変換する必要がありますIOExceptionIOException

Main をラップすることに問題はありませんcatch(Exception)(または、場合によってcatch(Throwable)は、プログラムが正常に終了できるようにするためです。ただし、必要な特定の例外を常にキャッチします。これにより、少なくとも、適切なエラーメッセージ。

人々が決して答えない質問はこれです:

RuntimeException サブクラスの代わりにサブクラスをスローする場合Exception 、何をキャッチする必要があるかをどうやって知るのでしょうか?

答えがキャッチの場合、Exceptionシステム例外と同じ方法でプログラマ エラーを処理しています。それは私には間違っているようです。

キャッチThrowableすると、システム例外と VM エラー (など) を同じ方法で処理します。それは私には間違っているようです。

答えが、スローされたことがわかっている例外のみをキャッチするというものである場合、どの例外がスローされたかをどうやって知るのでしょうか? プログラマー X が新しい例外をスローし、それをキャッチするのを忘れた場合はどうなりますか? それは私には非常に危険に思えます。

スタック トレースを表示するプログラムは間違っていると思います。チェック例外が嫌いな人はそう感じませんか?

では、チェック済み例外が気に入らない場合は、理由を説明し、回答されていない質問に回答していただけますか?

どちらのモデルをいつ使用するかについてのアドバイスを探しているわけではありません。私が探しているのは人々が拡張するのRuntimeExceptionが好きではないために拡張するException理由、および/または例外をキャッチRuntimeExceptionしてメソッドにスローを追加するのではなく、再スローする理由です。 . チェック例外を嫌う動機を理解したい。

4

32 に答える 32

310

私はあなたと同じブルース・エッケルのインタビューを読んだと思います - そしてそれはいつも私を悩ませています. 実際、この議論は、.NET と C# の背後にある MS の天才である Anders Hejlsberg によって行われました。

http://www.artima.com/intv/handcuffs.html

私は Hejlsberg と彼の作品のファンですが、この議論は常に私を偽物だと思っていました。それは基本的に次のように要約されます。

「チェック例外は悪いものです。なぜなら、プログラマーは常にそれらをキャッチして却下することでそれらを悪用し、そうでなければユーザーに提示される問題を隠したり無視したりすることにつながるからです」.

「そうでなければユーザーに提示される」とは、ランタイム例外を使用すると、怠惰なプログラマーはそれを無視し (空の catch ブロックでキャッチするのではなく)、ユーザーに表示されることを意味します。

議論の要約は、「プログラマーはそれらを適切に使用せず、適切に使用しないことは、それらを持たないよりも悪い」ということです。

この議論にはいくつかの真実があり、実際、Goslings が Java に演算子のオーバーライドを置かない動機は、同様の議論から来ているのではないかと思います。

しかし、結局のところ、私はそれが Hejlsberg の偽りの議論であり、よく考え抜かれた決定ではなく、欠落を説明するために作成された事後的な議論であることに気づきました。

チェック例外の過度の使用は悪いことであり、ユーザーによるずさんな処理につながる傾向がありますが、それらを適切に使用することで、API プログラマーは API クライアント プログラマーに大きな利益をもたらすことができると私は主張します。

ここで、API プログラマーはチェック例外をあちこちにスローしないように注意する必要があります。そうしないと、単にクライアント プログラマーを悩ませてしまいます。Hejlsberg が警告するように、非常に怠惰なクライアント プログラマーはキャッチ(Exception) {}に頼り、すべての利益が失われ、地獄が続くでしょう。しかし、状況によっては、適切なチェック済み例外に代わるものはありません。

私にとって、古典的な例はファイルオープン API です。言語の歴史 (少なくともファイル システム上) のすべてのプログラミング言語には、ファイルを開くことができる API がどこかにあります。そして、この API を使用するすべてのクライアント プログラマーは、開こうとしているファイルが存在しない場合に対処しなければならないことを知っています。言い換えると、この API を使用するすべてのクライアント プログラマーは、このケースに対処する必要があることを知っておく必要があります。API プログラマーは、コメントだけで対処する必要があることを彼らに知らせることができるでしょうか、それともクライアントに対処するように強く要求することができるでしょうか。

Cでは、イディオムは次のようになります

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

wherefopenは 0 を返すことで失敗を示し、C (愚かにも​​) は 0 をブール値として扱うことができます... 基本的に、このイディオムを学べば大丈夫です。しかし、あなたが初心者で、イディオムを学んでいない場合はどうでしょうか。そして、もちろん、あなたはから始めます

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

そして難しい方法を学びます。

ここでは、厳密に型指定された言語についてのみ話していることに注意してください。厳密に型指定された言語には、API が何であるかについての明確な考えがあります。それは、それぞれに明確に定義されたプロトコルで使用する機能 (メソッド) の寄せ集めです。

その明確に定義されたプロトコルは、通常、メソッド シグネチャによって定義されます。ここで fopen には、文字列 (または C の場合は char*) を渡す必要があります。それ以外のものを指定すると、コンパイル時エラーが発生します。プロトコルに従わなかった - API を適切に使用していません。

一部の (あいまいな) 言語では、戻り値の型もプロトコルの一部です。一部の言語で、変数に代入せずに同等のものを呼び出そうとするとfopen()、コンパイル時エラーも発生します (これは void 関数でのみ実行できます)。

私が言おうとしているポイントは次のとおりです。静的に型付けされた言語では、API プログラマーは、明らかな間違いがある場合にクライアント コードがコンパイルされないようにすることで、クライアントが API を適切に使用することを奨励します。

(Ruby のような動的に型付けされた言語では、float など、何でもファイル名として渡すことができ、それがコンパイルされます。メソッドの引数を制御するつもりもないのに、チェック例外でユーザーを煩わす必要はありません。ここで作成した引数は、静的に型付けされた言語にのみ適用されます。)

では、チェック例外はどうでしょうか。

ファイルを開くために使用できる Java API の 1 つを次に示します。

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

そのキャッチを見ますか?その API メソッドの署名は次のとおりです。

public FileInputStream(String name)
                throws FileNotFoundException

チェック例外でFileNotFoundExceptionあることに注意してください。

API プログラマーは次のように言っています。「このコンストラクターを使用して新しい FileInputStream を作成できますが、

a)ファイル名を文字列として渡す必要
がある b)実行時にファイルが見つからない可能性を受け入れる必要がある"

そして、それが私に関する限り、要点です。

重要なのは、基本的に質問が「プログラマーの管理外のもの」と述べていることです。私が最初に考えたのは、彼/彼女はAPIプログラマーが制御できないことを意味しているということでした。しかし実際には、適切に使用された場合のチェック済み例外は、クライアント プログラマーと API プログラマーの両方の制御の及ばないものに対するものであるべきです。これがチェック例外を悪用しないための鍵だと思います。

file-open はその点をうまく説明していると思います。API プログラマーは、API が呼び出された時点で存在しないことが判明したファイル名をユーザーに与える可能性があること、およびユーザーが要求したものを返すことができず、例外をスローする必要があることを知っています。また、これがかなり定期的に発生すること、およびクライアント プログラマーが呼び出しを記述した時点ではファイル名が正しいことを期待している可能性があることも知っていますが、実行時には制御できない理由で間違っている可能性もあります。

したがって、API はそれを明確にしています。電話をかけた時点でこのファイルが存在せず、対処したほうがよい場合があります。

これは、カウンターケースを使用するとより明確になります。テーブル API を書いているとします。このメソッドを含むAPIを使用して、テーブルモデルをどこかに持っています:

public RowData getRowData(int row) 

API プログラマーとして、一部のクライアントが行の負の値またはテーブル外の行の値を渡す場合があることを私は知っています。そのため、チェック済み例外をスローして、クライアントに強制的に処理させたいと思うかもしれません。

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(もちろん、「チェック済み」とは言いません。)

これは、チェック例外の不適切な使用です。クライアント コードは、行データを取得するための呼び出しでいっぱいになり、そのすべてで try/catch を使用する必要があります。間違った行が検索されたことをユーザーに報告しますか? おそらくそうではありません-テーブルビューを囲むUIが何であれ、ユーザーが不正な行が要求されている状態にならないようにする必要があるためです。したがって、これはクライアント プログラマー側のバグです。

API プログラマーは、クライアントがそのようなバグをコーディングし、IllegalArgumentException.

のチェック例外によりgetRowData、これは明らかに Hejlsberg の怠惰なプログラマーが単に空のキャッチを追加することにつながるケースです。その場合、不正な行の値は、テスターやクライアント開発者のデバッグにとっても明らかではなく、原因を突き止めるのが難しいノックオン エラーにつながります。アリアンロケットは打ち上げ後に爆破します。

さて、ここで問題があります。私が言いたいのは、チェック済み例外FileNotFoundExceptionは単なる良いものではなく、クライアント プログラマーにとって最も有用な方法で API を定義するための API プログラマー ツールボックスの不可欠なツールであるということです。しかし、これCheckedInvalidRowNumberExceptionは大きな不便であり、悪いプログラミングにつながるため、避けるべきです。しかし、違いを伝える方法。

それは正確な科学ではないと思いますが、それは Hejlsberg の主張の根底にあり、おそらくある程度正当化すると思います。しかし、私はここで赤ちゃんをお風呂の水で放り出すのは嬉しくないので、ここでいくつかのルールを抽出して、良いチェック例外と悪いチェック例外を区別できるようにします。

  1. クライアントの管理外またはクローズド vs オープン:

    チェック例外は、エラー ケースが APIクライアント プログラマの両方で制御できない場合にのみ使用する必要があります。これは、システムがどの程度開いている閉じているかに関係しています。クライアント プログラマーがすべてのボタン、テーブル ビューから行を追加および削除するキーボード コマンドなどを制御できる制約付きのUI (クローズド システム) では、クライアント プログラミングのバグです。存在しない行。任意の数のユーザー/アプリケーションがファイルを追加および削除できるファイルベースのオペレーティング システム (オープン システム) では、クライアントが要求しているファイルが知らないうちに削除されている可能性があるため、クライアントはそれを処理する必要があります。 .

  2. ユビキタス:

    クライアントによって頻繁に行われる API 呼び出しでは、チェック済み例外を使用しないでください。頻繁にというのは、クライアント コードの多くの場所からという意味です。時間的に頻繁ではありません。そのため、クライアント コードが同じファイルを何度も開こうとする傾向はありませんが、私のテーブル ビューはRowDataさまざまな方法であちこちに表示されます。特に、次のようなコードをたくさん書くつもりです。

    if (model.getRowData().getCell(0).isEmpty())
    

毎回try/catchでラップしなければならないのは面倒です。

  1. ユーザーへの通知:

    チェック例外は、エンド ユーザーに表示される有用なエラー メッセージを想像できる場合に使用する必要があります。これは「それが起こったときにあなたはどうしますか?」です。私が上で提起した質問。また、項目 1 にも関連しています。クライアント API システムの外部にある何かが原因でファイルがそこにない可能性があることを予測できるため、ユーザーにそのことを合理的に伝えることができます。

    "Error: could not find the file 'goodluckfindingthisfile'"
    

    不正な行番号は内部バグが原因であり、ユーザーの過失によるものではないため、ユーザーに提供できる有用な情報は実際にはありません。アプリで実行時例外がコンソールに表示されない場合、次のような醜いメッセージが表示される可能性があります。

    "Internal error occured: IllegalArgumentException in ...."
    

    つまり、クライアント プログラマーがユーザーに役立つ方法で例外を説明できないと思われる場合は、おそらくチェック済み例外を使用すべきではありません。

それが私のルールです。やや不自然で、間違いなく例外があります (必要に応じて、それらを改善するのを手伝ってください)。しかし、私の主な主張はFileNotFoundException、チェックされた例外がパラメーターの型と同じくらい重要で API コントラクトの一部として役立つ場合があるということです。ですから、悪用されたからといって手放すべきではありません。

申し訳ありませんが、これをそれほど長くてふざけたものにするつもりはありませんでした. 2 つの提案で締めくくります。

A: API プログラマー: チェック済み例外の有用性を維持するために、チェック例外は慎重に使用してください。疑わしい場合は、未チェックの例外を使用してください。

B: クライアント プログラマー: 開発の早い段階で、ラップされた例外を作成する (Google で検索する) 習慣を身につけてください。JDK 1.4 以降ではRuntimeException、このためのコンストラクターが提供されていますが、独自のコンストラクターを簡単に作成することもできます。コンストラクタは次のとおりです。

public RuntimeException(Throwable cause)

次に、チェック済み例外を処理する必要があり、怠惰に感じている場合 (または、API プログラマーが最初からチェック済み例外を使用することに熱心だったと思う場合) はいつでも、例外を単に飲み込むのではなく、ラップすることを習慣にします。そして投げ直します。

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

これを IDE の小さなコード テンプレートの 1 つに入れて、怠けているときに使用します。このようにして、チェックされた例外を本当に処理する必要がある場合は、実行時に問題が発生した後に戻って処理する必要があります。なぜなら、私 (そして Anders Hejlsberg) を信じてください。

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
于 2009-03-05T12:03:49.960 に答える
208

チェックされた例外に関することは、概念の通常の理解によると、それらは実際には例外ではないということです。代わりに、API 代替戻り値です。

例外の全体的な考え方は、呼び出しチェーンのどこかでスローされたエラーがバブルアップし、介在するコードがそれを気にすることなく、さらに上のコードによって処理される可能性があるということです。一方、チェック済み例外では、thrower と catcher の間のすべてのレベルのコードが、それらを通過する可能性のあるすべての形式の例外について知っていることを宣言する必要があります。これは実際には、チェックされた例外が、呼び出し元がチェックしなければならない特別な戻り値であった場合とほとんど変わりません。例 [疑似コード]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

Java は代替の戻り値や単純なインライン タプルを戻り値として実行できないため、チェック済み例外は合理的な応答です。

問題は、標準ライブラリの大部分を含む多くのコードが、チェック済み例外を実際の例外条件に対して誤用していることです。IOException が RuntimeException ではないのはなぜですか? 他のすべての言語では、IO 例外を発生させることができます。それを処理するために何もしないと、アプリケーションが停止し、便利なスタック トレースが表示されます。これは起こりうる最高のことです。

おそらく、ストリームへの書き込みプロセス全体からすべての IOExceptions をキャッチし、プロセスを中止して、エラー報告コードにジャンプしたい例から 2 つのメソッドがあります。Java では、それ自体が IO を行わないレベルであっても、すべての呼び出しレベルで 'throws IOException' を追加せずにそれを行うことはできません。このようなメソッドは、例外処理について知る必要はありません。署名に例外を追加する必要があります:

  1. 不必要にカップリングを増やします。
  2. インターフェイスの署名を変更するのが非常に脆弱になります。
  3. コードが読みにくくなります。
  4. 一般的なプログラマーの反応は、'throws Exception'、'catch (Exception e) {}'、またはすべてを RuntimeException にラップする (デバッグが難しくなる) などの恐ろしいことを行ってシステムを無効にすることです。

そして、次のようなばかげたライブラリ例外がたくさんあります。

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

このようにばかげたくだらないコードでコードをごちゃごちゃにしなければならない場合、チェック例外が多くの嫌悪感を抱くのも不思議ではありません.

別の特定の悪い影響は、コンポーネント A が汎用コンポーネント B にコールバックを提供する制御の反転です。コンポーネント A は、そのコールバックから、コンポーネント B を呼び出した場所に例外をスローできるようにしたいと考えていますが、できません。それは、B によって修正されたコールバック インターフェイスを変更するためです。A は、実際の例外を RuntimeException でラップすることによってのみ実行できます。

Java とその標準ライブラリに実装されているチェック済み例外は、ボイラープレート、ボイラープレート、ボイラープレートを意味します。すでに冗長な言語では、これは勝ちではありません。

于 2009-03-05T10:59:39.007 に答える
83

チェック例外に対するすべての (多くの) 理由を焼き直すのではなく、1 つだけを選びます。このコード ブロックを書いた回数を数え切れません。

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

99%の場合、私にはどうすることもできません。最後に、ブロックは必要なクリーンアップを行います (少なくともそうすべきです)。

また、これを見た回数のカウントを失いました:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

なんで?誰かがそれに対処しなければならず、怠け者だったからです。それは間違っていましたか?もちろん。それは起こりますか?絶対。代わりに、これがチェックされていない例外だったとしたら? アプリはただ死んでいたでしょう (これは、例外を飲み込むよりも望ましい方法です)。

そして、java.text.Formatのように、フロー制御の形式として例外を使用する腹立たしいコードがあります。ブズット。違う。フォームの数値フィールドに「abc」を入力するユーザーも例外ではありません。

わかりました、それが3つの理由だったと思います。

于 2009-03-05T11:44:29.460 に答える
63

私はこれが古い質問であることを知っていますが、私はチェックされた例外と格闘してしばらく過ごしてきました。長くなりますがご容赦ください!

チェック例外に関する私の主な不満は、それらがポリモーフィズムを台無しにすることです。それらをポリモーフィック インターフェイスでうまく動作させることは不可能です。

古き良き JavaListインターフェースを取り上げます。やのような一般的なメモリ内実装がArrayListありLinkedListます。AbstractListまた、新しいタイプのリストを簡単に設計できるスケルトン クラスもあります。読み取り専用リストの場合、実装する必要があるメソッドはsize()との 2 つだけget(int index)です。

このクラスの例では、ファイルからタイプ (表示されていません)WidgetListのいくつかの固定サイズ オブジェクトを読み取ります。Widget

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

使い慣れたインターフェイスを使用してウィジェットを公開することで、それ自体について知る必要なく、Listアイテムを取得したり ( list.get(123))、リストを反復処理したり( ) できます。このリストを、ジェネリック リストを使用する任意の標準メソッドに渡すか、. それを使用するコードは、「ウィジェット」がその場で構成されているか、配列から来ているか、ファイルやデータベースから読み取られているか、ネットワーク経由で読み取られているか、将来のサブスペースリレーから読み取られているかを知る必要も気にする必要もありません。インターフェイスが正しく実装されているため、引き続き正しく機能します。for (Widget w : list) ...WidgetListCollections.synchronizedListList

そうではないことを除いて。上記のクラスはコンパイルされません。これは、ファイル アクセス メソッドがIOException"キャッチまたは指定" する必要があるチェック済み例外である をスローする可能性があるためです。スローされるように指定することはできませんListインターフェイスの契約に違反するため、コンパイラは許可しません。そして、それ自体が例外を処理できる便利な方法はありませんWidgetList(後で詳しく説明します)。

どうやら唯一のことは、チェックされた例外をキャッチして、チェックされていない例外として再スローすることです。

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((編集: Java 8 はUncheckedIOException、まさにこのケースのためのクラスを追加しました: ポリモーフィック メソッドの境界を越えて s をキャッチして再スローするためのクラスですIOException。一種の私の主張を証明しています!))

したがって、チェック済み例外は、このような場合には機能しません。それらを投げることはできません。Mapデータベースに裏打ちされた巧妙な実装、またはjava.util.RandomCOMポートを介して量子エントロピーソースに接続された実装についても同様です。ポリモーフィック インターフェイスの実装で何か新しいことをしようとすると、チェック済み例外の概念はすぐに失敗します。しかし、チェックされた例外は非常に潜行性が高いため、依然として安心できません。なぜなら、低レベルのメソッドから例外をキャッチして再スローする必要があり、コードが乱雑になり、スタック トレースが乱雑になるからです。

Runnableチェックされた例外をスローする何かを呼び出す場合、ユビキタスなインターフェイスはしばしばこのコーナーに戻されます。例外をそのままスローすることはできないため、RuntimeException.

実際、ハッキングに頼ると、宣言されていないチェック例外をスローできます。JVM は、実行時にチェック例外規則を気にしないので、コンパイラだけをだます必要があります。これを行う最も簡単な方法は、ジェネリックを悪用することです。これは私のメソッドです((Java 8より前に)ジェネリックメソッドの呼び出し構文で必要なため、クラス名が表示されます):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

万歳!これを使用すると、チェック済みの例外を宣言せずに、スタックの任意の深さまでスローすることができますRuntimeException。「WidgetList」の例を再度使用します。

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

残念ながら、チェック例外の最後の侮辱は、欠陥のある意見ではスローされなかった場合、コンパイラがチェック例外をキャッチすることを拒否することです。(未チェックの例外にはこの規則はありません。) こっそりスローされた例外をキャッチするには、次のようにする必要があります。

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

これは少し厄介ですが、プラス面としては、RuntimeException.

幸いなことに、キャッチされた例外の再スローに関する Java 7 で追加されたルールのおかげでthrow t;、 の型がチェックされていても、ステートメントはここでは有効です。t


チェックされた例外が多態性を満たす場合、逆のケースも問題になります。メソッドがチェックされた例外をスローする可能性があると仕様されているが、オーバーライドされた実装はそうではない場合です。たとえば、抽象クラスOutputStreamwriteメソッドはすべて を指定しますthrows IOExceptionByteArrayOutputStream真の I/O ソースではなく、メモリ内配列に書き込むサブクラスです。そのオーバーライドされたwriteメソッドはIOExceptions を引き起こすことができないため、throws句がなく、キャッチまたは指定の要件を気にせずに呼び出すことができます。

常にではないことを除いて。ストリームWidgetに保存するメソッドがあるとします:

public void writeTo(OutputStream out) throws IOException;

プレーンを受け入れるようにこのメソッドを宣言するOutputStreamのは正しいことなので、ファイル、データベース、ネットワークなど、あらゆる種類の出力でポリモーフィックに使用できます。そしてインメモリ配列。ただし、メモリ内配列では、実際には発生しない例外を処理するという偽の要件があります。

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

いつものように、チェック例外が邪魔になります。変数が、より制限のない例外要件を持つ基本型として宣言されている場合、それらの例外がアプリケーションで発生しないことがわかっている場合でも、それらの例外のハンドラーを追加する必要があります。

しかし待ってください、チェック済み例外は実際には非常に煩わしいので、逆のこともできません! 現在、の呼び出しIOExceptionによってスローされたものをキャッチしているが、変数の宣言された型を に変更したいとします。writeOutputStreamByteArrayOutputStream

その規則は、いくつかのばかげた問題を引き起こします。たとえば、 の 3 つのwriteメソッドのうちの 1 つOutputStreamはによってオーバーライドされませんByteArrayOutputStream。具体的には、オフセット 0 と配列の長さをwrite(byte[] data)指定して呼び出すことにより、完全な配列を書き込む便利なメソッドです。引数が 3 つのメソッドをオーバーライドしますが、引数が 1 つの便利なメソッドをそのまま継承します。継承されたメソッドは正確に正しいことを行いますが、不要な句が含まれています。これはおそらく の設計上の見落としでしたが、例外をキャッチするコードとのソース互換性が損なわれるため、決して修正することはできません。write(byte[] data, int offset, int length)ByteArrayOutputStreamthrowsByteArrayOutputStream

そのルールは、編集中やデバッグ中も煩わしいものです。tryたとえば、メソッド呼び出しを一時的にコメント アウトすることがあります。チェック済みの例外がスローされた可能性がある場合、コンパイラはローカル ブロックとブロックの存在について文句を言うようになりcatchます。{そのため、それらもコメントアウトする必要があります。コードを編集すると、IDE が間違ったレベルにインデントさ}れます。ガッ!これは小さな不満ですが、チェックされた例外がこれまでに行う唯一のことは、問題を引き起こすことのようです。


ほぼ完了です。チェック済み例外に対する私の最後のフラストレーションは、ほとんどの呼び出しサイトで、それらを使ってできることは何もないということです。理想的には、何か問題が発生した場合、ユーザーに問題を通知したり、必要に応じて操作を終了または再試行したりできる、有能なアプリケーション固有のハンドラーが必要です。全体的な目標を知っている唯一のハンドラーであるため、スタックの上位にあるハンドラーのみがこれを実行できます。

代わりに、コンパイラをシャットダウンする方法として横行している次のイディオムを取得します。

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

GUI または自動化されたプログラムでは、出力されたメッセージは表示されません。さらに悪いことに、例外の後に残りのコードを処理します。例外は実際にはエラーではありませんか? それから印刷しないでください。そうしないと、他の何かがすぐに爆発し、その時までに元の例外オブジェクトはなくなります。このイディオムは、BASICOn Error Resume Nextや PHP のerror_reporting(0);.

ある種のロガークラスを呼び出すことは、それほど良いことではありません:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

それは怠け者e.printStackTrace();であり、不確定な状態のコードを処理します。さらに、特定のロギング システムまたは他のハンドラーの選択はアプリケーション固有であるため、コードの再利用に悪影響を及ぼします。

ちょっと待って!アプリケーション固有のハンドラーを見つける簡単で普遍的な方法があります。コールスタックの上位にあります(または、スレッドのキャッチされていない例外ハンドラーとして設定されています)。そのため、ほとんどの場合、スタックの上位に例外をスローするだけで済みます。例: throw e;. チェックされた例外は邪魔になります。

言語が設計されたとき、チェック例外は良いアイデアのように思えたに違いありませんが、実際には、それらはすべて煩わしく、メリットがないことがわかりました。

于 2013-11-27T02:03:24.187 に答える
47

まあ、それはスタック トレースを表示したり、静かにクラッシュしたりすることではありません。レイヤー間でエラーを通信できるようにすることです。

チェック例外の問題は、人々が重要な詳細 (つまり、例外クラス) を飲み込むことを助長することです。その詳細を飲み込まないことを選択した場合は、アプリ全体にスロー宣言を追加し続ける必要があります。これは、1) 新しい例外タイプが多くの関数シグネチャに影響すること、および 2) 実際にキャッチしたい例外の特定のインスタンスを見逃す可能性があることを意味します (たとえば、データを関数に書き込む関数のセカンダリ ファイルを開くとします)。セカンダリ ファイルはオプションなので、そのエラーは無視できますが、署名があるため、throws IOException見落としがちです)。

私は実際にアプリケーションでこの状況に対処しています。ほとんどの例外を AppSpecificException として再パッケージ化しました。これにより、署名が非常にきれいになり、署名の爆発について心配する必要がなくなりthrowsました。

もちろん、再試行ロジックなどを実装して、より高いレベルでエラー処理を特殊化する必要があります。ただし、すべて AppSpecificException であるため、「IOException がスローされた場合は再試行する」または「ClassNotFound がスローされた場合は完全に中止する」とは言えません。私たちのコードとサードパーティのコードの間を通過するときに物事が何度も再パッケージ化されるため、実際の例外に到達するための信頼できる方法はありません。

これが、私が Python の例外処理の大ファンである理由です。あなたが欲しいもの、および/または処理できるものだけを捕まえることができます。他のすべては、あたかも自分で再スローしたかのように泡立ちます(とにかくやりました)。

私は何度も何度も、そして私が言及したプロジェクトを通して、例外処理が3つのカテゴリに分類されることを発見しました:

  1. 特定の例外をキャッチして処理します。これは、たとえば、再試行ロジックを実装するためです。
  2. 他の例外をキャッチして再スローします。通常、ここで発生するのはログ記録だけであり、通常は「$filename を開けません」などの陳腐なメッセージが表示されます。これらは、どうしようもないエラーです。それを処理するのに十分なことを知っているのは、より高いレベルだけです。
  3. すべてをキャッチし、エラー メッセージを表示します。これは通常、ディスパッチャーの根元にあり、例外以外のメカニズム (ポップアップダイアログ、RPC エラーオブジェクトのマーシャリングなど) を介してエラーを呼び出し元に確実に伝えることができるようにします。
于 2009-03-05T09:40:37.057 に答える
26

SNR

まず、チェック例外はコードの「信号対ノイズ比」を低下させます。Anders Hejlsberg は、同様の概念である命令型プログラミングと宣言型プログラミングについても話しています。とにかく、次のコード スニペットを検討してください。

Java の非 UI スレッドから UI を更新します。

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

C# で非 UI スレッドから UI を更新します。

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

これは私にとってより明確に思えます。Swing でより多くの UI 作業を行うようになると、チェックされた例外が本当に面倒で役に立たなくなり始めます。

脱獄

Java の List インターフェイスなど、最も基本的な実装でさえ実装するために、契約による設計のためのツールとしてのチェック例外は失敗します。データベースやファイルシステム、またはチェック例外をスローするその他の実装によってサポートされるリストを考えてみましょう。唯一可能な実装は、チェックされた例外をキャッチして、チェックされていない例外として再スローすることです。

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

そして今、そのすべてのコードのポイントは何なのかを尋ねなければなりませんか? チェックされた例外はノイズを追加するだけであり、例外はキャッチされましたが処理されず、契約による設計 (チェックされた例外に関して) は崩壊しました。

結論

  • 例外をキャッチすることは、それらを処理することとは異なります。
  • チェックされた例外は、コードにノイズを追加します。
  • 例外処理は、それらがなくても C# でうまく機能します。

これについては以前ブログに書きました。

于 2009-04-01T03:25:32.227 に答える
21

記事「EffectiveJavaExceptions 」では、チェックされていない例外を使用する場合とチェックされている例外を使用する場合について詳しく説明しています。主なポイントを強調するために、その記事からの引用をいくつか示します。

不測の事態: メソッドの意図された目的の観点から表現できる、メソッドからの代替応答を要求する予想される状態。メソッドの呼び出し元は、これらの種類の条件を予期し、それらに対処するための戦略を持っています。

障害: メソッドの内部実装を参照せずに説明できない意図された目的をメソッドが達成できないようにする計画外の状態。

(SOはテーブルを許可しないため、元のページから以下を読むことをお勧めします...)

不測の事態

  • 次のように見なされます:設計の一部
  • 発生することが予想されます:定期的ですがめったにありません
  • 誰が気にするか:メソッドを呼び出すアップストリームコード
  • 例:代替リターンモード
  • 最適なマッピング:チェックされた例外

障害

  • 次のように考えられています:厄介な驚き
  • 起こると予想される:決して
  • 誰がそれを気にするか:問題を解決する必要がある人々
  • 例:プログラミングのバグ、ハードウェアの誤動作、構成の間違い、ファイルの欠落、サーバーの使用不可
  • 最適なマッピング:チェックされていない例外
于 2009-03-05T12:56:54.917 に答える
21

私は常にチェックされた例外を支持してきたので、最初はあなたに同意し、.Net でチェックされた例外を持たないのが好きではない理由について考え始めました。しかし、その後、私はチェック例外のように影響を与えていないことに気付きました。

あなたの質問に答えるために、はい、私は自分のプログラムがスタックトレースを表示するのが好きです。できれば本当に醜いものです。私は、アプリケーションが爆発して、あなたが見たいと思う最も醜いエラー メッセージの山になってほしいと思っています。

その理由は、もしそうなら、私はそれを修正しなければならず、すぐに修正しなければならないからです. 問題があることをすぐに知りたい。

実際に例外を何回処理しますか? 私は例外をキャッチすることについて話しているのではなく、それらを処理することについて話しているのですか? 次のように書くのはあまりにも簡単です。

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

そして、それは悪い習慣であり、「答え」は例外で何かをすることであると言うことができることを私は知っています(推測させてください、ログに記録しますか?)が、現実の世界(tm)では、ほとんどのプログラマーはそうしませんそれ。

そうです、そうする必要がなければ、例外をキャッチしたくありません。また、失敗したときにプログラムが見事に爆発することを望んでいます。静かに失敗することは、考えられる最悪の結果です。

于 2009-03-05T09:07:15.063 に答える
20

過去 3 年間、比較的複雑なアプリケーションで数人の開発者と協力してきました。適切なエラー処理で Checked Exceptions を頻繁に使用するコード ベースと、そうでないコード ベースがあります。

これまでのところ、Checked Exceptions を使用してコード ベースを操作する方が簡単であることがわかりました。他の誰かの API を使用している場合、コードを呼び出してログに記録、表示、または無視することで適切に処理したときに、どのような種類のエラー状態が予想されるかを正確に確認できると便利です (はい、無視する有効なケースがあります)。 ClassLoader 実装などの例外)。これにより、私が書いているコードに回復の機会が与えられます。すべての実行時例外は、キャッシュされ、一般的なエラー処理コードで処理されるまで伝播します。特定のレベルで実際には処理したくないチェック例外、またはプログラミング ロジック エラーと考えられるチェック例外を見つけた場合は、それを RuntimeException にラップしてバブルアップさせます。正当な理由なしに例外を飲み込むことは決してありません (これを行う正当な理由はかなり少ないです)。

チェックされた例外を持たないコードベースで作業する場合、関数を呼び出すときに何が期待できるかを事前に知るのが少し難しくなり、いくつかのものをひどく壊す可能性があります.

もちろん、これはすべて好みと開発者のスキルの問題です。プログラミングとエラー処理の両方の方法が同じように効果的 (または効果的でない) である可能性があるため、唯一の方法があるとは言えません。

全体として、特に多くの開発者がいる大規模なプロジェクトでは、Checked Exceptions を使用する方が簡単だと思います。

于 2009-03-05T09:40:12.130 に答える
16

例外カテゴリ

例外について話すとき、私はいつもEric Lippert の Vexing exceptionsブログ記事を参照します。彼は例外を次のカテゴリに分類しています。

  • 致命的- これらの例外はあなたのせいではありません。それを防ぐことはできず、賢明に処理することもできません。たとえば、OutOfMemoryErrorまたはThreadAbortException.
  • Boneheaded - これらの例外はあなたのせいです: あなたはそれらを防ぐべきでした、そしてそれらはあなたのコードのバグを表しています. たとえば、ArrayIndexOutOfBoundsExceptionNullPointerExceptionまたは任意のIllegalArgumentException.
  • 煩わしい- これらの例外は例外ではなく、あなたのせいでもありません。それらを防ぐことはできませんが、それらに対処する必要があります。多くの場合、解析の失敗時にブール値の false を返すメソッドを提供する代わりにNumberFormatExceptionfromをスローするなど、不幸な設計上の決定の結果です。Integer.parseIntInteger.tryParseInt
  • 外因性- これらの例外は通常例外的であり、あなたのせいではありません。(合理的に) 防止することはできませんが、処理する必要があります。たとえば、FileNotFoundException.

API ユーザー:

  • 致命的または骨の折れる例外を処理してはなりません。
  • 厄介な例外処理する必要がありますが、理想的な API では発生しないはずです。
  • 外因性の例外を処理する必要があります。

チェックされた例外

API ユーザーが特定の例外を処理する必要があるという事実は、呼び出し元と呼び出し先の間のメソッドの契約の一部です。コントラクトは、とりわけ、呼び出し先が期待する引数の数と型、呼び出し元が期待できる戻り値の型、呼び出し元が処理することが期待される例外を指定します。

煩わしい例外は API に存在してはならないため、メソッドのコントラクトの一部となるためには、これらの外因性の例外のみを例外としてチェックする必要があります。外因性の例外は比較的少ないため、どの API にもチェック例外は比較的少ないはずです。

チェック例外は、処理が必要な例外です。例外の処理は、それを飲み込むのと同じくらい簡単です。そこには!例外が処理されます。限目。開発者がそのように処理したい場合は、問題ありません。しかし、彼は例外を無視できず、警告を受けています。

API の問題

しかし、厄介で致命的な例外 (JCL など) をチェックAPIは、API ユーザーに不必要な負担をかけます。このような例外処理する必要がありますが、例外が非常に一般的であるため、そもそも例外であってはならないか、処理時に何も実行できません。これにより、Java 開発者はチェック例外を嫌うようになります。

また、多くの API には適切な例外クラス階層がなく、すべての種類の非外因性例外の原因が単一のチェック済み例外クラス (例: IOException) によって表されます。また、これにより、Java 開発者はチェック例外を嫌うようになります。

結論

外因性の例外とは、あなたのせいではなく、防ぐことができず、処理する必要があるものです。これらは、スローされる可能性のあるすべての例外の小さなサブセットを形成します。API は外因性の例外のみをチェックし、他のすべての例外はチェックしないようにする必要があります。これにより、より優れた API が作成され、API ユーザーの負担が軽減されるため、未チェックの例外をすべてキャッチしたり、飲み込んだり、再スローしたりする必要がなくなります。

したがって、Java とそのチェック例外を嫌いにならないでください。代わりに、チェック例外を過度に使用する API を嫌います。

于 2013-05-12T00:49:00.897 に答える
8

わかりました...チェックされた例外は理想的ではなく、いくつかの注意点がありますが、目的には役立ちます。API を作成する場合、この API の契約上の失敗の特定のケースがあります。Java などの強力に静的に型付けされた言語のコンテキストでは、チェック済み例外を使用しない場合、エラーの可能性を伝えるためにアドホック ドキュメントと規則に依存する必要があります。そうすることで、コンパイラーが処理エラーをもたらす可能性があるすべての利点が取り除かれ、プログラマーの善意に完全に委ねられます。

したがって、C# で行われたように Checked 例外を削除した場合、エラーの可能性をプログラム的および構造的にどのように伝えることができるでしょうか? そのようなエラーが発生する可能性があり、対処する必要があることをクライアントコードに通知する方法は?

チェックされた例外を扱うとき、あらゆる種類の恐怖を耳にします。それらは誤用されています。これは確かですが、チェックされていない例外も同様です。API が何層にもわたって深く積み上げられ、失敗を伝えるための何らかの構造化された手段の復活を懇願するようになるまで、数年待つと私は言います。

API レイヤーの最下部のどこかで例外がスローされ、このエラーが発生する可能性があることさえ誰も知らなかったためにバブルアップした場合を考えてみましょう。それを投げました(たとえば、VogonsTrashingEarthExceptとは対照的にFileNotFoundException ...この場合、処理するものが残っていないため、処理するかどうかは問題になりません)。

多くの人が、ファイルを読み込めないことは、ほとんどの場合、プロセスの世界の終わりであり、恐ろしく苦痛な死を遂げなければならないと主張しています. ええ..確かに...わかりました..あなたは何かのためにAPIを構築し、それはある時点でファイルをロードします...私はそのAPIのユーザーとして応答することしかできません...プログラムがクラッシュするはずです!」確かに例外が飲み込まれて痕跡を残さないか、EletroFlabbingChunkFluxManifoldChuggingException がマリアンナの塹壕よりも深いスタック トレースを持つ選択肢を考えると、ためらうことなく後者を選択しますが、これは例外を処理する望ましい方法であることを意味しますか? ? 実際に何かを意味するように、例外が新しいレベルの抽象化にトラバースするたびに、例外が再キャストされてラップされる中間のどこかにいることはできませんか?

最後に、私が目にする議論のほとんどは、「私は例外を扱いたくない。多くの人は例外を扱いたくない。チェック例外は私に例外処理を強制するので、チェック例外は嫌いだ」というものです。それを後藤地獄の裂け目に追いやるのは、ばかげているだけで、判断力とビジョンが欠けています。

チェックされた例外を排除すると、関数の戻り値の型を排除し、常に「任意の型」の変数を返すこともできます。

于 2009-08-07T17:34:53.607 に答える
8

これは、チェック例外の純粋な概念に対する反論ではありませんが、Java がチェック例外に使用するクラス階層は奇抜なショーです。私たちは常に物事を単に「例外」と呼んでいます – 言語仕様でもそう呼んでいるので、これは正しいです – しかし、例外はどのように命名され、型システムで表現されるのでしょうか?

Exception想像するクラスによって?s は例外であり、s ではない例外を除いてException同様に例外もsであるため、他の例外は実際にはs であり、これは別の種類の例外であり、例外的な例外の一種であり、場合によってはキャッチする必要があります。s でもs でもなく、単なる例外である他の例外を定義することもできるため、それだけではありません。Exception ExceptionErrorExceptionErrorThrowable

これらのうち、「チェックされた」例外はどれですか? Throwables は、チェックErrorされていない例外である s でもある場合を除いて、チェックされた例外です。次に、Exceptions でもありThrowable、チェックされた例外の主なタイプである s があります。もRuntimeExceptions です。これは、別の種類の非チェック例外であるためです。

s は何RuntimeExceptionのためにあるのですか? 名前が示すように、それらはすべてExceptionの s と同様に例外であり、実際にはすべての例外と同様に実行時に発生しRuntimeExceptionますExceptionRuntimeExceptions は決してs ではありませんが、ばかげたエラーを犯した場合、Errorそれらは例外的にエラーであるが実際にはErrors ではないもののためのものです。for を除いてRuntimeErrorException、これは実際にはRuntimeExceptionforErrorです。しかし、いずれにせよ、すべての例外が誤った状況を表しているわけではないのでしょうか? はい、すべてです。を除いてThreadDeath、非常に例外的ではない例外です。ドキュメントでは、それは「通常の発生」であり、「Error

とにかく、すべての例外を途中Errorで s (例外的な実行例外用であるため、チェックされていない) とExceptions (それほど例外的な実行エラー用ではないため、チェックされていない場合を除いてチェックされている) に分割しているため、2 つが必要になります。いくつかの例外のそれぞれの種類。したがって、 and 、 and and 、 and and 、 and IllegalAccessErrorand 、and andが必要です。IllegalAccessExceptionInstantiationErrorInstantiationExceptionNoSuchFieldErrorNoSuchFieldExceptionNoSuchMethodErrorNoSuchMethodExceptionZipErrorZipException

ただし、例外がチェックされている場合でも、コンパイラをごまかしてチェックせずに例外をスローする (かなり簡単な) 方法が常に存在します。、または( 「重大な例外」のみである、とは無関係)UndeclaredThrowableExceptionとしてスローされる可能性がある他の場合を除いて、 を取得する可能性があります。UnexpectedExceptionUnknownExceptionUnknownErrorExecutionExceptionInvocationTargetExceptionExceptionInInitializerError

ああ、Java 8 のおしゃれな new を忘れてはなりません。これは、I/O エラー (例外は存在しますが、例外は発生しません) によって引き起こされたチェック済み例外をラップすることによって、例外チェックの概念を窓の外に放り出せるUncheckedIOExceptionように設計された例外ですRuntimeException。あまりにも)扱いが非常に難しいため、チェックしないようにする必要があります。IOExceptionIOError

ありがとうジャバ!

于 2016-04-14T15:09:10.110 に答える
6

この記事は、私がこれまで読んだ Java での例外処理に関する最高のテキストです。

チェックされた例外よりもチェックされていない例外が優先されますが、この選択は非常に徹底的に説明され、強力な議論に基づいています。

ここで記事の内容をあまり引用したくはありませんが (全体として読むのが最善です)、このスレッドからのチェックされない例外の支持者のほとんどの議論をカバーしています。特に、この引数 (非常に人気があるようです) がカバーされています。

API レイヤーの最下部のどこかで例外がスローされ、このエラーが発生する可能性があることさえ誰も知らなかったためにバブルアップした場合を考えてみましょう。それを投げました(たとえば、VogonsTrashingEarthExceptとは対照的にFileNotFoundException ...この場合、処理するものが残っていないため、処理するかどうかは問題になりません)。

著者は次のように「応答」します。

すべての実行時例外がキャッチされるべきではなく、アプリケーションのまさに「最上位」に伝搬されることを許可されるべきではないと想定するのは、まったく正しくありません。(...) システム/ビジネス要件によって明確に処理する必要がある例外的な条件ごとに、プログラマーはそれをどこでキャッチするか、条件がキャッチされたら何をするかを決定する必要があります。これは、コンパイラの警告に基づくのではなく、アプリケーションの実際のニーズに厳密に従って行う必要があります。他のすべてのエラーは、それらがログに記録される最上位のハンドラーに自由に伝播できるようにする必要があり、適切な (おそらく終了) アクションが実行されます。

そして、主な考えや記事は次のとおりです。

ソフトウェアでのエラー処理に関しては、存在するすべてのサブルーチンまたはモジュールでエラーが発生する可能性があるという唯一の安全で正しい仮定があります。

したがって、「このエラーが発生する可能性さえ誰も知らなかった」場合、そのプロジェクトには何か問題があります。このような例外は、著者が示唆するように、少なくとも最も一般的な例外ハンドラ (より具体的なハンドラによって処理されないすべての例外を処理するもの) によって処理されるべきです。

残念なことに、この素​​晴らしい記事を発見した人は多くないようです :-(. どのアプローチが良いか迷っている人には、時間をかけて読んでみることを心からお勧めします.

于 2011-12-31T09:48:25.957 に答える
6

問題

例外処理メカニズムで見られる最悪の問題は、コードの重複が大規模に発生することです。正直に言うと、95% の時間のほとんどのプロジェクトで、開発者が例外に対して実際に行う必要があるのは、何らかの方法でユーザーに (場合によっては開発チームにも) 伝えることだけです。 -スタック トレースをメールで送信します)。したがって、通常、例外が処理されるすべての場所で同じコード行/ブロックが使用されます。

ある種のチェック済み例外について、各 catch ブロックで単純なログを記録するとします。

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

これが一般的な例外である場合、より大きなコードベースには、そのような try-catch ブロックが数百もある可能性があります。ここで、コンソール ログの代わりにポップアップ ダイアログ ベースの例外処理を導入するか、開発チームに追加の電子メールを送信する必要があるとします。

ちょっと待ってください...コード内の数百の場所をすべて編集するつもりですか?! あなたは私の主張を理解します:-)。

ソリューション

この問題に対処するために私たちが行ったことは、例外処理を一元化するための例外ハンドラー(後で EH と呼びます)の概念を導入することでした。例外を処理する必要があるすべてのクラスに、依存性注入フレームワークによって例外ハンドラーのインスタンスが注入されます。したがって、例外処理の典型的なパターンは次のようになります。

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

例外処理をカスタマイズするには、コードを 1 か所 (EH コード) だけ変更する必要があります。

もちろん、より複雑なケースでは、EH のいくつかのサブクラスを実装し、DI フレームワークが提供する機能を活用できます。DI フレームワークの構成を変更することで、EH 実装をグローバルに簡単に切り替えたり、特殊な例外処理が必要なクラスに EH の特定の実装を提供したりできます (たとえば、Guice @Named アノテーションを使用)。

そうすれば、アプリケーションの開発バージョンとリリース バージョンで例外処理動作を区別することができます (たとえば、開発 - エラーをログに記録してアプリケーションを停止し、製品 - エラーを詳細にログに記録し、アプリケーションの実行を続行させる)。

最後にひとこと

大事なことを言い忘れましたが、トップレベルの例外処理クラスに到達するまで例外を「上に」渡すだけで、同じ種類の集中化が得られるように見えるかもしれません。しかし、それはメソッドのコードと署名の乱雑につながり、このスレッドで他の人が言及したメンテナンスの問題を引き起こします.

于 2011-10-28T22:01:38.120 に答える
6

実際、チェック例外は、一方ではプログラムの堅牢性と正確性を高めます (インターフェイスを正しく宣言する必要があります。メソッドがスローする例外は、基本的に特別な戻り値の型です)。一方、例外が「バブルアップ」するため、例外を変更するときに、非常に多くのメソッド (すべての呼び出し元、および呼び出し元の呼び出し元など) を変更する必要があるという問題に直面します。メソッドがスローされます。

Java のチェック例外は後者の問題を解決しません。C# と VB.NET は赤ちゃんを風呂の水と一緒に捨てます。

この OOPSLA 2005 の論文(または関連するテクニカル レポート)には、中道の優れたアプローチが説明されています。

つまりmethod g(x) throws like f(x)、g は f がスローするすべての例外をスローするということです。出来上がり、カスケード変更の問題なしで例外をチェックしました。

これは学術論文ですが、チェック例外の利点と欠点をうまく説明しているため、(その一部を) 読むことをお勧めします。

于 2009-03-05T12:23:02.440 に答える
5

未回答の質問だけに対処するには:

Exception サブクラスの代わりに RuntimeException サブクラスをスローした場合、何をキャッチする必要があるかをどうやって知るのでしょうか?

質問には、私見の疑わしい推論が含まれています。API が何をスローするかを教えてくれるからといって、すべての場合で同じように扱うわけではありません。別の言い方をすれば、キャッチする必要がある例外は、例外をスローするコンポーネントを使用するコンテキストによって異なります。

例えば:

データベースの接続テスター、または XPath に入力されたユーザーの有効性をチェックするものを作成している場合、操作によってスローされるすべてのチェック済みおよび未チェックの例外をキャッチしてレポートしたいと思うでしょう。

ただし、処理エンジンを作成している場合は、XPathException (チェック済み) を NPE と同じように扱う可能性があります。ワーカー スレッドの先頭まで実行させ、残りのバッチをスキップし、ログに記録します。問題を報告 (または診断のためにサポート部門に送信) し、ユーザーがサポートに連絡できるようにフィードバックを残します。

于 2012-07-04T13:21:47.477 に答える
4

c2.com に関する私の記事は、まだ元の形式からほとんど変更されていません: CheckedExceptionsAreIncompatibleWithVisitorPattern

要約すれば:

ビジター パターンとそれに関連するものは、間接呼び出し元とインターフェイス実装の両方が例外を認識しているが、インターフェイスと直接呼び出し元が認識できないライブラリを形成するインターフェイスのクラスです。

CheckedExceptions の基本的な前提は、宣言されたすべての例外は、その宣言でメソッドを呼び出す任意のポイントからスローできるということです。VisitorPattern は、この仮定が間違っていることを示しています。

このような場合のチェック例外の最終結果は、実行時にコンパイラのチェック例外制約を本質的に削除する、それ以外の場合は役に立たない多くのコードです。

根本的な問題については:

私の一般的な考えは、最上位のハンドラーが例外を解釈し、適切なエラー メッセージを表示する必要があるというものです。ほとんどの場合、IO 例外、通信例外 (何らかの理由で API が区別)、またはタスクの致命的なエラー (プログラムのバグまたはバッキング サーバーでの重大な問題) のいずれかを確認します。サーバーの問題。

于 2009-11-07T20:34:38.873 に答える
3

プログラマーは、メソッドを正しく使用するために、メソッドがスローする可能性のあるすべての例外を知っている必要があります。したがって、いくつかの例外で彼を圧倒しても、不注意なプログラマーがエラーを回避するのに必ずしも役立つとは限りません。

わずかなメリットよりも、負担の大きいコストが勝ります (特に、インターフェイス シグネチャを常に変更することが実用的でない、大規模で柔軟性の低いコード ベースでは)。

静的分析は素晴らしいものですが、真に信頼できる静的分析は、多くの場合、プログラマーに厳格な作業を要求する柔軟性に欠けます。費用対効果の計算があり、コンパイル時のエラーにつながるチェックの基準を高く設定する必要があります。IDE が、メソッドがスローする可能性のある例外 (避けられない例外を含む) を伝達する役割を担うと、より便利になります。強制的な例外宣言がないと信頼性が低下する可能性がありますが、ほとんどの例外はドキュメントで宣言されており、IDE 警告の信頼性はそれほど重要ではありません。

于 2015-07-16T02:03:04.120 に答える
3

これは優れた質問であり、まったく議論の余地がないと思います。サードパーティのライブラリは(一般に)チェックされていない例外をスローする必要があると思います。これは、ライブラリへの依存関係を分離できることを意味します (つまり、例外を再スローしたり、例外をスローしたりする必要はありませんException。通常は悪い習慣です)。Spring のDAO レイヤーは、この優れた例です。

一方、コア Java API からの例外は、通常、処理できるかどうかを確認する必要がありますFileNotFoundExceptionまたは(私のお気に入り)を取りInterruptedExceptionます。これらの条件は、ほとんどの場合、具体的に処理する必要があります (つまり、 に対する反応は、 に対するInterruptedException反応と同じではありませんIllegalArgumentException)。例外がチェックされるという事実により、開発者は条件が処理可能かどうかを考える必要があります。(とは言っても、InterruptedException適切に処理されているのはめったに見たことがありません!)

もう1つ-aRuntimeExceptionは常に「開発者が何か間違った場所」であるとは限りません。enumusingを作成しようとして、その名前valueOfがない場合、無効な引数の例外がスローされます。enumこれは必ずしも開発者のミスではありません!

于 2009-03-05T10:00:40.083 に答える
1

ページ全体を読んだにもかかわらず、チェック済み例外に対する妥当な議論はまだ 1 つも見つかりません。代わりに、ほとんどの人は、一部の Java クラスまたは独自のクラスの API 設計が貧弱であると話しています。

この機能が煩わしい唯一のシナリオは、プロトタイピングです。これは、言語にメカニズムを追加することで解決できます (たとえば、@suppresscheckedexceptions アノテーション)。しかし、通常のプログラミングでは、チェック例外は良いことだと思います。

于 2011-08-23T06:52:43.977 に答える
1

C# のチーフ アーキテクトへの言及をいくつか見てきました。

チェック例外をいつ使用するかについて、Java 担当者からの別の視点を次に示します。彼は、他の人が言及した多くの否定的な点を認めています: 効果的な例外

于 2012-08-02T16:54:02.687 に答える
0

(ほとんどの場合) チェック済み例外の存在について満足しているとは言えませんが、私は例外処理について多くのことを読んできました。これは私の見解です。 、OS など) と、高レベル API/アプリケーション レベルでの未チェックの例外。

それらの間に線を引くのはそれほど簡単ではありませんが、常に多くのチェック済み例外をラップせずに同じ屋根の下に複数の API/ライブラリを統合することは本当に面倒/困難であることがわかりますが、一方で、時にはいくつかの例外をキャッチし、現在のコンテキストでより意味のある別の例外を提供することを強制する方が便利です/より良いです。

私が取り組んでいるプロジェクトは、多くのライブラリを取り、それらを同じ API の下に統合します。この API は、チェックされていない例外に完全に基づいています。例外(初期化例外、構成例外など)と私はあまり友好的ではなかったと言わなければなりません. ほとんどの場合、処理方法がわからない、または気にしない (例外を無視する必要があると混同しないでください) 例外をキャッチまたは再スローする必要がありました。クリックすると、10 個の (チェックされた) 例外がスローされる可能性があります。

現在のバージョン (3 番目のバージョン) は、チェックされていない例外のみを使用し、キャッチされていないものすべてを処理するグローバルな例外ハンドラーを備えています。API は例外ハンドラーを登録する方法を提供します。例外ハンドラーは、例外がエラーと見なされるかどうか (ほとんどの場合これが当てはまります) を決定します。これは、ログに記録して誰かに通知することを意味します。または、この例外、AbortException、これは、現在の実行スレッドを中断し、エラーをログに記録しないことを意味します。もちろん、すべてのカスタム スレッドを機能させるには、run() メソッドを try {...} catch(all) で処理する必要があります。

public void run() {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

すべてを処理する WorkerService を使用してジョブ (Runnable、Callable、Worker) をスケジュールする場合、これは必要ありません。

もちろん、これは私の意見であり、正しいものではないかもしれませんが、私には良いアプローチのように見えます. プロジェクトをリリースした後、それが私にとって良いと思うか、他の人にとっても良いかどうかを確認します... :)

于 2009-05-18T03:36:06.293 に答える
0

チェックされた例外に対する 1 つの引数を次に示します (joelonsoftware.com から)。

その理由は、例外は、コードのあるポイントから別のポイントへの突然のジャンプを作成するという点で、1960 年代から有害であると考えられていた「goto」よりも優れているとは考えていないからです。実際、それらは goto のものよりもかなり悪いです:

  • それらはソース コードでは見えません。例外をスローする場合とスローしない場合がある関数を含むコードのブロックを見ても、どの例外がどこからスローされるかを確認する方法はありません。これは、注意深くコードを検査しても、潜在的なバグが明らかにならないことを意味します。
  • 関数の出口点が多すぎる可能性があります。正しいコードを記述するには、関数を介して考えられるすべてのコード パスについて考える必要があります。例外を発生させる可能性のある関数を呼び出すたびに、その場でそれをキャッチしないと、関数が突然終了したり、データが一貫性のない状態のままになったり、その他の予期しないコード パスが原因で、予期せぬバグが発生する機会が生じます。について考える。
于 2009-03-05T09:12:51.040 に答える
-2

私の意見では、チェック例外は非常に優れた概念です。残念ながら、一緒に仕事をしたほとんどのプログラマーは別の意見を持っているため、プロジェクトには多くの間違った例外処理が使用されています。私は、ほとんどのプログラマーが 1 つ (1 つだけ) の例外クラス、つまり RuntimeException のサブクラスを作成することを見てきました。これには、メッセージ、場合によっては多言語キーが含まれています。私はこれに反論する機会がありません。アンチパターンとは何か、メソッドのコントラクトとは何かを説明するとき、私は壁に向かって話しているような印象を受けます... 私は少しがっかりしています。

しかし今日では、すべてに対して一般的な実行時例外を設定するという概念がアンチ パターンであることが明らかになりました。ユーザー入力のチェックに使用しています。例外がスローされるため、ユーザー ダイアログでエラー メッセージを作成できます。しかし、メソッドのすべての呼び出し元がダイアログであるとは限りません! 実行時例外をスローすることにより、メソッドのコントラクトが変更されましたが、宣言されていませんでした。これは、チェックされた例外ではないためです。

願わくば、彼らが今日何かを学んで、別の場所で (有用で必要な) チェックを行ってくれることを願っています。チェックされた例外のみを使用しても問題は解決できませんでしたが、チェックされた例外は、プログラマーが何か間違ったことを実装していることを知らせます。

于 2013-08-22T22:11:25.263 に答える