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

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

何年もの間、私は次の質問に対する適切な答えを得ることができませんでした。なぜ一部の開発者はチェック例外にそれほど反対しているのでしょうか? 私は何度も会話をし、ブログを読み、ブルース・エッケル (チェック例外に反対する声を初めて見た人) の意見を読みました。

現在、新しいコードをいくつか書いていて、例外の処理方法に細心の注意を払っています。「チェック例外は好きではない」という人たちの視点を理解しようとしていますが、まだ理解できません。

私が交わす会話はすべて、同じ質問に回答されないまま終わってしまいます... 設定してみましょう:

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

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

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

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

「終了するだけだ」という議論については、終了する場合でも、適切なエラー メッセージを表示する必要があると私は言います。エラーの処理を先延ばしにしているだけでは、明確な理由が示されずにプログラムが終了したときに、ユーザーはそれほど満足しないでしょう。

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

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

人々が決して答えない質問は次のとおりです。

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

答えが catch である場合Exception、プログラマー エラーもシステム例外と同じ方法で処理していることになります。これは間違っているように思います。

キャッチするとThrowable、システム例外と VM エラー (および同様のもの) を同じ方法で処理することになります。これは間違っているように思われます。

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

スタック トレースを表示するプログラムは間違っていると思います。チェック例外を好まない人はそう感じないのでしょうか。

それで、チェック例外が気に入らないのであれば、その理由を説明して、答えられていない質問にも答えてもらえませんか?

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

ベストアンサー1

あなたと同じ Bruce Eckel のインタビューを読んだと思いますが、ずっと気になっていました。実際、この議論は、インタビューを受けた人 (これがあなたが言及している投稿であれば) である Anders Hejlsberg によってなされました。彼は .NET と C# の背後にいる MS の天才です。

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

私はヘイルスバーグと彼の作品のファンですが、この議論は私にはいつも偽物に思えてきました。要するに、次のようになります。

「チェック例外は良くありません。なぜなら、プログラマーはチェック例外を常にキャッチして無視することでそれを乱用し、本来はユーザーに提示されるはずの問題が隠されたり無視されたりするからです。」

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

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

この議論にはある程度の真実があり、実際、Java に演算子オーバーライドを置かないという Gosling の動機も同様の議論から来ているのではないかと思います。つまり、演算子オーバーライドは頻繁に悪用されるため、プログラマーを混乱させるということです。

しかし結局のところ、私はそれがヘイルスバーグの根拠のない議論であり、よく考え抜かれた決定ではなく、不足を説明するために事後的に作られた議論であると考えている。

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

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

For me, the classic example is the file-open API. Every programming language in the history of languages (on file systems at least) has an API somewhere that lets you open a file. And every client programmer using this API knows that they have to deal with the case that the file they are trying to open doesn't exist. Let me rephrase that: Every client programmer using this API should know that they have to deal with this case. And there's the rub: can the API programmer help them know they should deal with it through commenting alone or can they indeed insist the client deal with it.

In C the idiom goes something like

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

where fopen indicates failure by returning 0 and C (foolishly) lets you treat 0 as a boolean and... Basically, you learn this idiom and you're okay. But what if you're a noob and you didn't learn the idiom. Then, of course, you start out with

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

and learn the hard way.

Note that we're only talking about strongly typed languages here: There's a clear idea of what an API is in a strongly typed language: It's a smorgasbord of functionality (methods) for you to use with a clearly defined protocol for each one.

That clearly defined protocol is typically defined by a method signature. Here fopen requires that you pass it a string (or a char* in the case of C). If you give it something else you get a compile-time error. You didn't follow the protocol - you're not using the API properly.

In some (obscure) languages the return type is part of the protocol too. If you try to call the equivalent of fopen() in some languages without assigning it to a variable you'll also get a compile-time error (you can only do that with void functions).

The point I'm trying to make is that: In a statically typed language the API programmer encourages the client to use the API properly by preventing their client code from compiling if it makes any obvious mistakes.

(In a dynamically typed language, like Ruby, you can pass anything, say a float, as the file name - and it will compile. Why hassle the user with checked exceptions if you're not even going to control the method arguments. The arguments made here apply to statically-typed languages only.)

So, what about checked exceptions?

Well here's one of the Java APIs you can use for opening a file.

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

See that catch? Here's the signature for that API method:

public FileInputStream(String name)
                throws FileNotFoundException

Note that FileNotFoundException is a checked exception.

The API programmer is saying this to you: "You may use this constructor to create a new FileInputStream but you

a) must pass in the file name as a String
b) must accept the possibility that the file might not be found at runtime"

And that's the whole point as far as I'm concerned.

The key is basically what the question states as "Things that are out of the programmer's control". My first thought was that he/she means things that are out of the API programmers control. But in fact, checked exceptions when used properly should really be for things that are out of both the client programmer's and the API programmer's control. I think this is the key to not abusing checked exceptions.

I think the file-open illustrates the point nicely. The API programmer knows you might give them a file name that turns out to be nonexistent at the time the API is called, and that they won't be able to return you what you wanted, but will have to throw an exception. They also know that this will happen pretty regularly and that the client programmer might expect the file name to be correct at the time they wrote the call, but it might be wrong at runtime for reasons beyond their control too.

So the API makes it explicit: There will be cases where this file doesn't exist at the time you call me and you had damn well better deal with it.

This would be clearer with a counter-case. Imagine I'm writing a table API. I have the table model somewhere with an API including this method:

public RowData getRowData(int row) 

Now as an API programmer I know there will be cases where some client passes in a negative value for the row or a row value outside of the table. So I might be tempted to throw a checked exception and force the client to deal with it:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(I wouldn't really call it "Checked" of course.)

This is bad use of checked exceptions. The client code is going to be full of calls to fetch row data, every one of which is going to have to use a try/catch, and for what? Are they going to report to the user that the wrong row was sought? Probably not - because whatever the UI surrounding my table view is, it shouldn't let the user get into a state where an illegal row is being requested. So it's a bug on the part of the client programmer.

The API programmer can still predict that the client will code such bugs and should handle it with a runtime exception like an IllegalArgumentException.

With a checked exception in getRowData, this is clearly a case that's going to lead to Hejlsberg's lazy programmer simply adding empty catches. When that happens, the illegal row values will not be obvious even to the tester or the client developer debugging, rather they'll lead to knock-on errors that are hard to pinpoint the source of. Arianne rockets will blow up after launch.

Okay, so here's the problem: I'm saying that the checked exception FileNotFoundException is not just a good thing but an essential tool in the API programmers toolbox for defining the API in the most useful way for the client programmer. But the CheckedInvalidRowNumberException is a big inconvenience, leading to bad programming and should be avoided. But how to tell the difference.

これは正確な科学ではないと思いますし、それが 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)を信じてください、あなたは決してそのTODOに戻ることはないでしょう。

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

おすすめ記事