web-dev-qa-db-ja.com

チェック例外に対するケース

何年もの間、私は次の質問にきちんとした答えを得ることができませんでした:なぜ一部の開発者はチェックされた例外に対してそうですか?私は多くの会話をし、ブログで物事を読み、ブルース・エッケルが言ったことを読みました(私が見た最初の人が彼らに向かって話しました)。

私は現在、いくつかの新しいコードを書いており、例外にどう対処するかに非常に注意を払っています。 「チェックされた例外は嫌いだ」という群衆の視点を見ようとしていますが、まだ見えません。

私が行ったすべての会話は、同じ質問が未回答のまま終了する...それを設定させてください:

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

  • エラーは決してキャッチされるべきものではない(VMにはピーナッツアレルギーがあり、誰かがピーナッツの瓶を落とした)
  • RuntimeExceptionは、プログラマーが間違ったことをしたためのものです(プログラマーは配列の端から離れました)
  • 例外(RuntimeExceptionを除く)は、プログラマが制御できないものです(ファイルシステムへの書き込み中にディスクがいっぱいになり、プロセスのファイルハンドルの制限に達したため、これ以上ファイルを開くことができません)
  • Throwableは、すべての例外タイプの親です。

私が聞く一般的な議論は、例外が発生した場合、開発者はプログラムを終了するだけです。

私が聞く別の一般的な議論は、チェックされた例外がコードのリファクタリングを難しくするということです。

「私がやろうとしているのはexitだけ」の引数については、終了しようとしても、妥当なエラーメッセージを表示する必要があると言います。エラーの処理をパントしているだけの場合は、理由を明確に示すことなくプログラムが終了したときにユーザーが過度に満足することはありません。

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

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

人々が決して返事をしない質問はこれです:

Exceptionサブクラスの代わりにRuntimeExceptionサブクラスをスローする場合、どのようにキャッチするべきかをどのように知るのですか?

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

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

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

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

したがって、チェック済みの例外が気に入らない場合は、理由を説明してください。答えられない質問には答えてください。

編集:どちらのモデルを使用するかについてのアドバイスは探していませんが、私が探しているのはwhy例外からの拡張および/またはなぜ例外をキャッチするのが好きではないのでRuntimeExceptionから拡張する人ですそして、メソッドにスローを追加するのではなく、RuntimeExceptionを再スローします。チェック例外を嫌う動機を理解したいと思います。

427
TofuBeer

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

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

私はヘイルスベルクと彼の作品のファンですが、この議論はいつも偽物だと思いました。基本的には次のように要約されます。

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

"それ以外の場合はユーザーに表示されます"ランタイム例外を使用する場合、怠zyなプログラマーはそれを無視します(空のcatchブロックでキャッチするのではなく) )、ユーザーに表示されます。

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

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

しかし、最終的に、私はそれをヘイルスベルグの偽の議論であり、おそらく十分に考え抜かれた決定ではなく、不足を説明するために作成された事後の議論だと思います。

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

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

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

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

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

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

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

難しい方法を学びます。

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

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

一部の(あいまいな)言語では、戻り値の型もプロトコルの一部です。一部の言語でfopen()に相当するものを変数に割り当てずに呼び出そうとすると、コンパイル時エラーも発生します(これはvoid関数でのみ可能です)。

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

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

それでは、チェックされた例外はどうですか?

これが、ファイルを開くために使用できるJava AP​​Iの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

FileNotFoundExceptionchecked例外であることに注意してください。

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

a)mustファイル名を文字列として渡す
b)mustは、実行時にファイルが見つからない可能性を受け入れます。

そして、それは私に関する限り、全体のポイントです。

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

ファイルを開くと、ポイントがうまく示されていると思います。 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の怠zyなプログラマーにつながるケースです。その場合、不正な行の値はテスターやクライアント開発者のデバッグにとっても明白ではなく、ソースを特定するのが難しいノックオンエラーにつながります。アリアンヌロケットは打ち上げ後に爆発します。

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

私はそれが正確な科学ではないと思うし、それがヘイルスベルクの議論の根底にあり、おそらく正当化されると思う。しかし、私はここでお風呂で赤ちゃんを捨てることに満足していないので、ここでいくつかのルールを抽出して、チェック済みの良い例外と悪いものを区別することができます:

  1. クライアントの制御不能またはクローズvsオープン:

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

  2. ユビキティー:

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

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

    毎回try/catchでラップするのはつらいでしょう。

  3. ユーザーへの通知:

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

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

    不正な行番号は内部バグが原因であり、ユーザーの過失が原因ではないため、実際に提供できる有用な情報はありません。アプリがランタイム例外をコンソールにフォールさせない場合、おそらく次のようないメッセージが表示されます:

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

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

それが私のルールです。多少不自然で、間違いなく例外があります(もしそうなら、私がそれらを洗練するのを手伝ってください)。しかし、私の主な論点は、FileNotFoundExceptionのような、チェックされた例外がパラメータータイプと同じくらい重要でAPIコントラクトの一部であるケースがあるということです。そのため、誤用されているという理由だけでそれを省くべきではありません。

申し訳ありませんが、これをそれほど長くてワッフルにするつもりはありませんでした。最後に2つの提案を行います。

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

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

public RuntimeException(Throwable cause)

その後、チェック例外を処理する必要があり、怠feelingだと感じる(または、最初にチェック例外を使用することにAPIプログラマーが熱心だと思う)ときはいつも、例外を飲み込むのではなく、それをラップする習慣を身につけますそしてそれを再スローします。

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

これをIDEの小さなコードテンプレートの1つに配置し、怠feelingなときに使用します。このように、チェックされた例外を本当に処理する必要がある場合、実行時に問題を確認した後に戻って対処する必要があります。私を信じて(そしてアンダース・ヘイルスバーグ)、あなたはあなたのTODOに決して戻ってこない

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
266
Rhubarb

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

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

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では、すべての呼び出しレベルで「IOExceptionをスロー」を追加せずにそれを行うことはできません。それ自体がIOを実行しないレベルであってもです。このようなメソッドは、例外処理について知る必要はありません。署名に例外を追加する必要があります:

  1. カップリングを不必要に増加させます。
  2. インターフェイスシグネチャを非常に変更しにくいものにします。
  3. コードを読みにくくします。
  4. 一般的なプログラマーの反応は、「例外を投げる」、「キャッチ(例外e){}」のような恐ろしいことを行うか、RuntimeExceptionですべてをラップする(デバッグを難しくする)ことによってシステムを打ち負かすことです。

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

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

このように馬鹿げたコードでコードを乱雑にしなければならない場合、実際にはこれは単純な貧弱なAPI設計であるにもかかわらず、チェックされた例外が多くの憎しみを受け取るのも不思議ではありません。

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

Javaおよびその標準ライブラリで実装されているチェック済み例外は、定型文、定型文、定型文を意味します。すでに冗長な言語では、これは勝利ではありません。

179
bobince

チェック済み例外に対するすべての(多くの)理由を再ハッシュするのではなく、1つだけを選択します。このコードブロックを記述した回数のカウントを失いました。

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

99%の時間、私はそれについて何もできません。最後に、ブロックは必要なクリーンアップを実行します(または、少なくともそうする必要があります)。

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

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

どうして?誰かがそれに対処しなければならず、怠け者だったからです。間違っていましたか?承知しました。それは起こりますか?絶対に。これが代わりに未チェックの例外であった場合はどうなりますか?アプリは死んだばかりです(例外を飲み込むよりも望ましい)。

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

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

74
cletus

これは古い質問であることは知っていますが、チェック済みの例外と格闘している間に、何か追加することがあります。その長さを許してください!

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

良いol 'Java List インターフェイスを使用してください。 ArrayListLinkedListなどの一般的なメモリ内実装があります。また、骨格クラス AbstractList があり、新しいタイプのリストを簡単に設計できます。読み取り専用リストの場合は、size()get(int index)の2つのメソッドのみを実装する必要があります。

この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インターフェースを使用してウィジェットを公開することにより、WidgetList自体について知る必要なく、アイテムを取得(list.get(123))したり、リストを繰り返し処理(for (Widget w : list) ...)したりできます。このリストを、汎用リストを使用する標準メソッドに渡すか、Collections.synchronizedListでラップできます。それを使用するコードは、「ウィジェット」がその場で作成されるのか、配列から取得されるのか、ファイル、データベース、ネットワーク経由、または将来のサブスペースリレーから読み取られるのかを知る必要もありません。 Listインターフェイスが正しく実装されているため、引き続き正常に機能します。

そうでないことを除いて。上記のクラスは、ファイルアクセスメソッドがIOExceptionをスローする可能性があるため、コンパイルされません。これは、「キャッチまたは指定」する必要があるチェック済み例外です。あなたはthrowedとして指定することはできません-コンパイラーはあなたを許可しません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 クラスを追加しました。ポリモーフィックメソッドの境界を越えてIOExceptionsをキャッチして再スローするためです。

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

ユビキタスな Runnable インターフェースは、チェック例外をスローする何かを呼び出すと、このコーナーにしばしばバックされることがわかります。例外をそのままスローすることはできないため、RuntimeExceptionとしてキャッチして再スローすることでコードを乱雑にするだけです。

実際、ハックに頼ると、宣言されていないチェック済み例外をcanスローできます。 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);
    }
}

残念ながら、チェック済み例外の最後のin辱は、コンパイラーが、欠陥のある意見で、チェック済み例外をcatchすることを許可しないことです。投げられた。 (チェックされていない例外にはこのルールはありません。)こっそりスローされた例外をキャッチするには、これを行う必要があります。

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でラップされたチェック済み例外を抽出するためのコードよりも少し簡単です。

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


チェック例外がポリモーフィズムを満たしている場合、反対のケースも問題です。メソッドがチェック例外をスローする可能性があると指定されているが、オーバーライドされた実装はそうではない場合。たとえば、抽象クラス OutputStreamwriteメソッドはすべてthrows IOExceptionを指定します。 ByteArrayOutputStream は、真の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);
}

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

しかし、待って、チェックされた例外は実際にはso迷惑であり、彼らはあなたが逆をすることさえできません!あなたが現在キャッチしていると想像してくださいIOExceptionによってスローされたwriteは、OutputStreamを呼び出しますが、変数の宣言された型をByteArrayOutputStreamに変更したい場合、コンパイラーは、スローできないという確認済みの例外をキャッチしようとしてあなたを非難します。

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

このルールは、編集およびデバッグ中にも迷惑です。たとえば、メソッド呼び出しを一時的にコメントアウトすることがありますが、チェックされた例外をスローできた場合、コンパイラはローカルのtryおよびcatchブロックの存在について文句を言うでしょう。したがって、それらもコメントアウトする必要があります。そして、コードを編集するとき、{}がコメントアウトされているため、IDEは間違ったレベルにインデントされます。ああ!ちょっとした苦情ですが、チェックされた例外が原因となるのはトラブルの原因だけです。


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

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

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

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

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

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

これはe.printStackTrace();と同様に怠zyであり、依然として不確定状態のコードを処理します。さらに、特定のロギングシステムまたは他のハンドラーの選択はアプリケーション固有であるため、コードの再利用が損なわれます。

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

例外は、言語が設計されたときに良いアイデアのように聞こえることを確認しましたが、実際には、それらはすべて面倒であり、利点がないことがわかりました。

46
Boann

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

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

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

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

だからこそ、私はpythonの例外処理の大ファンです。必要なものだけをキャッチしたり、処理したりできます。他のすべては、自分でそれを再スローしたかのようにバブルします(とにかくこれを行いました)。

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

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

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#でうまく機能します。

これについてブログに書いた 以前

24
Luke Quinane

Artima インタビューを公開 .NETの設計者の1人であるAnders Hejlsbergは、チェックされた例外に対する議論を鋭くカバーしています。短いテイスター:

Throws句は、少なくともJavaで実装されている方法では、必ずしも例外の処理を強制するわけではありませんが、例外を処理しない場合は、どの例外が通過する可能性があるかを正確に確認する必要があります。宣言された例外をキャッチするか、独自のthrows句に入れる必要があります。この要件を回避するために、人々はばかげたことをします。たとえば、すべてのメソッドを「例外をスロー」で装飾します。それは機能を完全に打ち負かすだけで、プログラマーにもっと派手なネバネバしたものを書かせただけです。それは誰にも役に立たない。

22
Le Dude

私は常にチェック例外に賛成してきたので、最初にあなたに同意しました。しかし、その後、私はチェック例外のような事実ではないことに気付きました。

あなたの質問に答えるために、はい、私は私のプログラムがスタックトレース、できれば本当にlikeいものを表示するのが好きです。私は、あなたが見たいと思うcouldいエラーメッセージの恐ろしいヒープにアプリケーションを爆発させたいです。

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

実際に何回例外を処理しますか?私は例外をキャッチすることについて話しているのではなく、例外を処理することについて話しているのですか?次の記述は簡単すぎます。

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

そして、あなたはそれが悪い習慣であり、「答え」は例外で何かをすることであると言うことができることを知っています(推測させてください、ログに記録しますか?)それ。

そのため、そうする必要がない場合は例外をキャッチしたくありません。黙って失敗することは、最悪の結果です。

20
tsimon

記事 Effective Java Exceptions は、いつuncheckedを使用し、いつchecked例外を使用するかをうまく説明しています。主なポイントを強調するために、その記事からの引用をいくつか示します。

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

Fault:メソッドの内部実装を参照せずに記述することのできない、メソッドが意図した目的を達成できないようにする計画外の状態。

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

偶発事象

  • 考えられる:デザインの一部
  • 起こることが予想される:定期的だがまれに
  • 誰がそれを気にする:メソッドを呼び出す上流のコード
  • 例:代替リターンモード
  • ベストマッピング:チェック済み例外

障害

  • と考えられている:厄介な驚き
  • 起こると予想される:決して
  • 誰がそれを気にするか:問題を解決する必要がある人々
  • 例:プログラミングのバグ、ハードウェアの誤動作、構成の誤り、ファイルの欠落、利用できないサーバー
  • ベストマッピング:未確認の例外
20
Esko Luontola

要するに:

例外はAPI設計の質問です。-これ以上でもそれ以下でもありません。

チェック済み例外の引数:

チェックされた例外が良いことではない理由を理解するために、質問を振り返って尋ねましょう:チェックされた例外がいつ、またはなぜ魅力的か、つまり、コンパイラに例外の宣言を強制するのはなぜですか?

答えは明らかです:時々、need例外をキャッチする必要があり、呼び出されるコードが特定の例外クラスを提供する場合にのみ可能ですに興味があります。

したがって、引数forチェック例外は、コンパイラーがプログラマーにどの例外がスローされるかを宣言することを強制することであり、願わくばプログラマーは特定の例外クラスとその原因となるエラーも文書化しますそれら。

しかし実際には、パッケージcom.acmeは特定のサブクラスではなくAcmeExceptionのみをスローする場合があります。その後、呼び出し元はAcmeExceptionsを処理、宣言、または再通知する必要がありますが、AcmeFileNotFoundErrorが発生したのかAcmePermissionDeniedErrorが発生したのかを確認することはできません。

したがって、AcmeFileNotFoundErrorのみに関心がある場合、ソリューションはACMEプログラマーに機能要求を提出し、AcmeExceptionのサブクラスを実装、宣言、および文書化するように指示することです。

だからわざわざ?

したがって、チェックされた例外があっても、コンパイラはプログラマに有用例外をスローさせることはできません。 APIの品質の問題に過ぎません。

その結果、チェックされた例外のない言語は通常、それほど悪くはなりません。プログラマーは、Errorではなく、一般的なAcmeExceptionクラスの不特定のインスタンスをスローするように誘惑されるかもしれませんが、APIの品質を気にするなら、結局AcmeFileNotFoundErrorを導入することを学ぶでしょう。

全体として、例外の仕様とドキュメントは、たとえば通常のメソッドの仕様とドキュメントとそれほど違いはありません。これらもAPI設計の問題であり、プログラマーが便利な機能を実装またはエクスポートするのを忘れた場合、APIを改善して、便利に機能できるようにする必要があります。

この推論の行に従うと、Javaのような言語で非常に一般的な例外の宣言、キャッチ、および再スローの「面倒」がほとんど価値をもたらさないことは明らかです。

Java VMがnot例外をチェックしていないことにも注意してください-Javaコンパイラのみそれらをチェックし、例外宣言が変更されたクラスファイルは実行時に互換性があります。 Java VMセキュリティはチェック例外によって改善されず、コーディングスタイルのみが向上します。

19

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

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

チェック例外を持たないコードベースで作業する場合、関数を呼び出すときに何が期待できるかを事前に知るのが少し難しくなります。

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

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

14
Mario Ortegón

例外カテゴリ

例外について話すとき、私はいつも Eric Lippertの厄介な例外 ブログ記事を参照します。彼はこれらのカテゴリに例外を置きます:

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

APIユーザー:

  • 必須ではないハンドル致命的またはboneheaded例外。
  • should処理vexing例外ですが、理想的なAPIでは発生しません。
  • mustハンドルexogenous例外。

チェックされた例外

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

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

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

APIの問題

ただし、vexingおよびfatal例外(JCLなど)をチェックしたAPIは、APIユーザーに不必要な負担をかけます。そのような例外haveは処理されますが、例外は非常に一般的であるため、そもそも例外であってはならないか、処理時に何も実行できません。 thisを使用すると、Java開発者はチェック例外を嫌います。

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

結論

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

Javaとそのチェックされた例外を嫌いにしないでください。代わりに、チェック例外を使いすぎるAPIを嫌います。

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

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

チェックされた例外を処理するとき、私はあらゆる種類の恐怖を聞きます、彼らは誤用されていますこれは確かですが、チェックされていない例外もそうです。 APIが何層にも積み上げられ、障害を伝えるために何らかの構造化された手段が戻ってくることを願っています。

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

多くの人は、ファイルをロードできなかったことがプロセスの世界の終わりであり、恐ろしく痛みを伴う死で死ななければならないと主張しました。確かに...確かに... OK ..何かのAPIを作成し、ある時点でファイルをロードします...私が言ったAPIのユーザーは応答することしかできません...プログラムがクラッシュするはずです!」はい?実際に何かを意味するように、新しいレベルの抽象化に移動するたびに例外が再キャストされラップされる中間のどこかにいることはできませんか?

最後に、私が見る議論のほとんどは、「例外に対処したくない、多くの人々は例外に対処したくない。チェックされた例外はそれらに対処せざるをえないので、チェックされた例外を嫌う」です。後藤地獄の溝にそれを委ねるのは愚かであり、判断力とビジョンが欠けています。

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

6
Newtopian

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

Javaのチェックされた例外は、後者の問題を解決しません。 C#とVB.NETは、お風呂で赤ちゃんを捨てます。

中間の道を取るニースのアプローチは、 このOOPSLA 2005の論文 (または 関連技術レポート に記載されています。)

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

これは学術論文ですが、チェックされた例外の利点と欠点を説明するのに良い仕事をするので、それを読むことをお勧めします。

6

Andersは、チェックされた例外の落とし穴と、ソフトウェアエンジニアリングラジオの episode 97 でC#から除外した理由について話しています。

5
Chuck Conway

問題

例外処理メカニズムで見られる最悪の問題は、大規模でコードの重複を導入することです!正直に言うと、95%の時間のほとんどのプロジェクトで、開発者が本当に例外なく行う必要があるのは、なんらかの方法でユーザーに(そして、場合によっては、e -スタックトレース付きのメール)。したがって、通常、例外が処理されるすべての場所で同じコード行/ブロックが使用されます。

あるタイプのチェック済み例外に対して、各catchブロックで単純なロギングを行うと仮定しましょう。

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

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

ちょっと待って...コード内の数百の場所すべてを本当に編集するつもりですか?!あなたは私のポイントを得ます:-)。

ソリューション

この問題を解決するために行ったのは、例外ハンドラーの概念を導入することでした(これをさらにEHと呼びます)からcentralize 例外処理。例外を渡す必要のあるすべてのクラスに、例外ハンドラのインスタンスが Dependency Injection フレームワークによって注入されます。したがって、例外処理の典型的なパターンは次のようになります。

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

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

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

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

最後に一つ

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

5
Piotr Sobczyk

すでに述べたように、Javaバイトコードにはチェック例外は存在しません。これらは単なるコンパイラメカニズムであり、他の構文チェックとは異なります。コンパイラーが冗長条件:if(true) { a; } b;について文句を言うのと同じように、チェックされた例外をよく見ます。これは役に立ちますが、意図的にこれを行った可能性がありますので、警告を無視してください。

問題の事実は、チェックされた例外を強制する場合、すべてのプログラマーに「正しいことをする」ように強制することはできません。

悪いプログラムを修正してください!それらを許可しないように言語を修正しようとしないでください!ほとんどの人にとって、「例外について何かをする」というのは、実際にユーザーにそれを伝えることです。同様に、未チェックの例外についてもユーザーに伝えることができるので、チェック済みの例外クラスをAPIから除外します。

5
Martin

未回答の質問のみに対処しようとするには:

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

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

例えば:

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

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

5
Dave Elton

チェックされた例外は、元の形式では、障害ではなく偶発事象を処理する試みでした。称賛に値する目標は、特定の予測可能なポイント(接続できない、ファイルが見つからないなど)を強調し、開発者がこれらを処理できるようにすることでした。

元のコンセプトに決して含まれていなかったのは、広範にわたる体系的かつ回復不能な障害を強制的に宣言することでした。これらの障害は、チェック例外として宣言されることは決してありませんでした。

通常、コードで障害が発生する可能性があり、EJB、Web、およびSwing/AWTコンテナは、最も外側の「失敗した要求」例外ハンドラーを提供することにより、すでにこれに対応しています。最も基本的な正しい戦略は、トランザクションをロールバックしてエラーを返すことです。

重要な点の1つは、実行時例外とチェック済み例外は機能的に同等です。チェック済み例外が実行できる処理または回復はなく、ランタイム例外は実行できないことです。

「チェック済み」例外に対する最大の論点は、ほとんどの例外を修正できないことです。単純な事実は、壊れたコード/サブシステムを所有していないということです。実装を確認できず、責任を負わず、修正することもできません。

アプリケーションがDBでない場合、DBを修正しようとするべきではありません。これは、カプセル化の原則に違反します

特に問題となっているのは、JDBC(SQLException)とEJBのRMI(RemoteException)の領域です。元の「チェック例外」の概念に従って修正可能な偶発事象を特定するのではなく、実際に修正可能ではないこれらの広範に及ぶシステムの信頼性の問題を広く宣言する必要があります。

Java設計のもう1つの重大な欠陥は、例外処理を可能な限り最高の「ビジネス」または「リクエスト」レベルに正しく配置する必要があることでした。ここでの原則は、「早く投げ、遅れて捕まえる」ことです。チェックされた例外はほとんど機能しませんが、これを妨げます。

Javaには明らかな問題があり、何千もの何もしないtry-catchブロックが必要で、かなりの割合(40%+)が誤ってコーディングされています。これらのいずれも、真の処理や信頼性を実装していませんが、大きなコーディングオーバーヘッドを課しています。

最後に、「チェック済み例外」は、FP関数型プログラミングとほとんど互換性がありません。

「すぐに処理する」という彼らの主張は、「キャッチレイト」例外処理のベストプラクティスと、ループや制御の流れを抽象化するFP構造の両方と対立しています。

多くの人々は「処理」チェック例外について話しますが、彼らの帽子を通して話します。失敗した後、null、不完全、または不正なデータを使用してpretendを成功させても、何も処理されません。それは最も低い形式のエンジニアリング/信頼性の不正です。

きれいに失敗することは、例外を処理するための最も基本的な正しい戦略です。トランザクションをロールバックし、エラーをログに記録し、ユーザーに「失敗」応答を報告することは適切なプラクティスです。最も重要なことは、誤ったビジネスデータがデータベースにコミットされるのを防ぐことです。

例外処理のその他の戦略は、ビジネス、サブシステム、または要求レベルでの「再試行」、「再接続」、または「スキップ」です。これらはすべて一般的な信頼性戦略であり、実行時例外でうまく機能します。

最後に、正しくないデータで実行するよりも、失敗する方がはるかに望ましいです。続行すると、元の原因から離れた2次エラーが発生し、デバッグが困難になります。または最終的には誤ったデータがコミットされることになります。人々はそのために解雇されます。

見る:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

4
Thomas W

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

チェック済みの例外よりも未チェックの方が有利ですが、この選択は非常に詳細に説明されており、強力な議論に基づいています。

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

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

著者の「応答」:

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

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

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

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

多くの人々がこの素晴らしい記事を発見するのは悲しいようです:-(。私は、どちらのアプローチをためらうかは、時間をかけて読むことをお勧めします。

4
Piotr Sobczyk

C2.comの記事は、元の形式からほとんど変更されていません。 CheckedExceptionsAreIncompatibleWithVisitorPattern

要約すれば:

訪問者パターンとその親類は、間接呼び出し側とインターフェース実装の両方が例外について知っているが、インターフェースと直接呼び出し側は知らないライブラリを形成するインターフェースのクラスです。

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

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

根本的な問題に関して:

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

4
Joshua

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

クラスごと Exception いいえ、Exceptionsは例外であり、同様に例外はExceptionsです。ただし、notExceptions、他の例外は実際には Error sであり、これは他の種類の例外、発生する場合を除いて決して発生してはならない例外的な例外であり、キャッチする必要がある場合を除きます。 ExceptionsでもErrorsでもなく、単なる Throwable 例外である他の例外も定義できるため、それだけではありません。

これらのうち、「チェック済み」例外はどれですか? Throwablesはチェック済み例外です。ただし、それらがErrorsである場合は例外であり、それらはExceptionsであり、それからThrowablesもあります。また、 RuntimeException sです。これは、他の種類の未チェックの例外だからです。

RuntimeExceptionsとは何ですか?名前が示すように、それらはすべてのExceptionsのような例外であり、実際にすべての例外と同様に実行時に発生しますが、例外としてRuntimeExceptionsは他のランタイムExceptionsと比較して例外的である何かおかしなエラーを犯したときは、RuntimeExceptionsは決してErrorsではないので、例外的なエラーであるが実際にはErrorsではないものを対象としています。 RuntimeErrorException を除いて、これは実際にはRuntimeExceptionsのErrorです。しかし、とにかくすべての例外が誤った状況を表しているとは限りませんか?はい、それらのすべて。 ThreadDeath を除き、例外ではない例外ですが、ドキュメントでは「通常の発生」であり、そのためにErrorのタイプになっていることが説明されています。

とにかく、すべての例外を真ん中のErrors(例外的な実行例外のため、チェックされていない)とExceptions(例外的でない実行エラーのため、そうでない場合を除いてチェックされる)に分割しているので、2つ必要になりましたいくつかの例外のそれぞれの異なる種類。したがって、 IllegalAccessErrorIllegalAccessException 、および InstantiationErrorInstantiationException 、および NoSuchFieldError および NoSuchFieldException 、および NoSuchMethodError および NoSuchMethodException 、および ZipError および ZipException

例外をチェックする場合でも、コンパイラをチートしてチェックせずにスローする(かなり簡単な)方法が常にあります。それを行うと、 UndeclaredThrowableException を取得する場合がありますが、他の場合を除き、 UnexpectedException または UnknownExceptionUnknownError とは無関係で、「重大な例外」専用)、または ExecutionException 、または InvocationTargetException 、または ExceptionInInitializerError

ああ、Java 8のおしゃれな新しい UncheckedIOException を忘れてはいけません。これは、チェックラップすることでウィンドウから例外チェックの概念をスローできるように設計されたRuntimeException例外です IOException I/Oエラーによって引き起こされる例外( IOError 例外は発生しませんが、例外も存在します)それらをチェックしないようにする必要があります。

Javaに感謝します!

4
Boann

チェック例外の問題は、インターフェイスの実装が1つでも使用している場合、そのインターフェイスのメソッドに例外が付加されることが多いことです。

チェック例外の別の問題は、それらが誤用される傾向があることです。この完璧な例は Java.sql.Connectionclose() メソッドです。既に明示的に述べられているであるにもかかわらず、 SQLException をスローできます。 close()でどの情報が気になるかを伝えることができますか?

通常、connection*をclose()すると、次のようになります。

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

また、さまざまな解析メソッドとNumberFormatExceptionを開始しないでください。例外をスローしない.NETのTryParseは非常に使いやすいので、Javaに戻る必要があるのは痛いです。 (私はJavaと私が働いているC#の両方を使用しています)。

*追加のコメントとして、PooledConnectionのConnection.close()は接続を均等化closeしませんが、チェック例外であるため、SQLExceptionをキャッチする必要があります。

3
Powerlord

ここ チェック例外に対する1つの引数(joelonsoftware.comから):

その理由は、1960年代から有害であると考えられている「goto's」よりも例外が優れていると考えているためです。実際、それらはgotoのものよりも著しく悪い:

  • それらはソースコードでは見えません。例外をスローする場合としない場合がある関数を含むコードのブロックを見ると、どの例外がどこからスローされたかを確認する方法はありません。これは、慎重なコード検査でも潜在的なバグが明らかにならないことを意味します。
  • それらは、関数に対して非常に多くの可能な出口点を作成します。正しいコードを書くためには、関数を通るすべての可能なコードパスについて本当に考える必要があります。例外を発生させ、その場でキャッチしない関数を呼び出すたびに、突然終了した関数に起因する不意のバグが発生し、データが一貫性のない状態のままになったり、他のコードパスがなかったについて考える。
3
finnw

プログラマは、メソッドを正しく使用するために、メソッドがスローする可能性のある例外をall知る必要があります。そのため、いくつかの例外だけで頭を打ち負かしても、不注意なプログラマーがエラーを回避できるとは限りません。

スリムな利点は負担の大きいコストを上回ります(特に、インターフェイスシグネチャを絶えず変更することが実用的ではない、より大きく柔軟性の低いコードベースでは)。

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

3

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

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

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

2
oxbow_lakes

Checked Exceptionが不要であることを証明しているのは次のとおりです。

  1. Javaの一部を機能させる多くのフレームワーク。 JDBC例外を未チェックの例外にラップし、メッセージをログにスローするSpringのように
  2. Javaプラットフォームの上でも、Javaの後に来た多くの言語-それらは使用しません
  3. 例外を確認しました。これは、クライアントが例外をスローするコードをどのように使用するかについての親切な予測です。しかし、このコードを作成する開発者は、コードのクライアントが動作しているシステムとビジネスについては決して知りません。例として、チェック例外をスローすることを強制するInterfcaceメソッド。システムには100の実装があり、50または90の実装でもこの例外はスローされませんが、ユーザーがそのインターフェイスを参照する場合、クライアントはこの例外をキャッチする必要があります。これらの50または90の実装は、内部でこれらの例外を処理する傾向があり、例外をログに記録します(そして、これはそれらにとって良い動作です)。それで何をすべきか?ログにメッセージを送信するという、すべてのジョブを実行するバックグラウンドロジックが必要です。そして、コードのクライアントとして、例外を処理する必要があると感じたら、それを実行します。忘れてしまうかもしれませんが、TDDを使用すると、すべての手順がカバーされ、何が欲しいかがわかります。
  4. JavaでI/Oを使用している別の例では、ファイルが存在しない場合、すべての例外を強制的にチェックしますか?私はそれで何をすべきか?存在しない場合、システムは次のステップに進みません。このメソッドのクライアントは、そのファイルから期待されるコンテンツを取得しません-彼はランタイム例外を処理できます。いいえ...いいえ-RuntimeEceptionを使用して自動的に実行することをお勧めします。手動で処理する意味はありません。ログにエラーメッセージが表示されたら幸いです(AOPはそれを助けます。Javaを修正する何か)。最終的に、システムがエンドユーザーにポップアップメッセージを表示する必要があると判断した場合、問題ではなく表示します。

Javaが、I/Oなどのコアライブラリを操作するときに、使用するchoiceを提供してくれると嬉しかったです。 Likeは同じクラスの2つのコピーを提供します-1つはRuntimeEceptionでラップされます。 その後、人々が使用するものを比較できます。ただし、現時点では、多くの人がJavaまたは異なる言語のフレームワークを使用する方が良いでしょう。 Scalaのように、JRubyは何でも。多くの人は、太陽が正しかったと信じています。

2
ses

誰も言及していない重要なことの1つは、インターフェイスとラムダ式をどのように妨害するかです。

MyAppException extends Exceptionを定義するとしましょう。これは、アプリケーションによってスローされるすべての例外によって継承される最上位の例外です。すべてのメソッドはthrows MyAppExceptionを宣言します。これは少し面倒ですが、管理しやすいものです。例外ハンドラーは例外をログに記録し、何らかの方法でユーザーに通知します。

あなたのものではないインターフェイスを実装するまで、すべてが問題ないように見えます。明らかに、MyApExceptionをスローする意図を宣言していないため、コンパイラはそこから例外をスローすることを許可しません。

ただし、例外がRuntimeExceptionを拡張する場合、インターフェイスに問題はありません。必要に応じて、JavaDocで例外を自発的に言及できます。しかし、それ以外は何もせずに静かにバブルし、例外処理レイヤーでキャッチされます。

2
Vlasec

C#のチーフアーキテクトへの言及がいくつかあります。

これは、チェック例外をいつ使用するかについてのJavaガイからの別の視点です。彼は、他の人が言及した多くのネガティブを認めます: 効果的な例外

1
Jacob Toronto

ページ全体を読んだにもかかわらず、チェックされた例外に対する単一の妥当な引数を見つけることができません。代わりに、ほとんどの人は、いくつかのJavaクラスまたは自分のクラスで、貧弱なAPI設計について話しています。

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

0
Mister Smith

(ほとんどの場合)チェック例外の存在について満足または悲しみを言うことができない場合でも、例外処理について多くのことを読みました。これは私の意見です。低レベルコードのチェック例外(IO、ネットワーキング、OSなど)および高レベルAPI /アプリケーションレベルの未確認の例外。

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

私が取り組んでいるプロジェクトは多くのライブラリを取り、それらを同じAPI、つまり未チェックの例外に完全に基づいたAPIの下に統合します。このフレームワークは、最初はチェック済みの例外でいっぱいであり、いくつかの未チェックのAPI exceptions(Initialization Exception、ConfigurationExceptionなど)および私は言わなかった友好的。ほとんどの場合、処理方法がわからない例外をキャッチまたは再スローする必要がありました。または、気にしない(例外を無視するべきであると混同しないでください)、特にクライアント側ではクリックすると、可能性のある(チェック済み)例外が10個スローされる可能性があります。

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

public void run(){

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

}

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

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

0
adrian.tarau