私はあなたがしたのと同じブルース・エッケルのインタビューを読んだと思います-それはいつも私を悩ませています。実際、この議論はインタビュー対象者によって行われました(これが本当にあなたが話している投稿である場合)。.NETとC#の背後にあるMSの天才であるAnders Hejlsbergです。
http://www.artima.com/intv/handcuffs.html
私はHejlsbergと彼の作品のファンですが、この議論は常に私を偽物として感じました。基本的には次のようになります。
「チェックされた例外は悪いです。なぜなら、プログラマーは常にそれらをキャッチして却下することによって悪用するだけであり、そうでなければユーザーに提示されるはずの問題が隠されて無視されることにつながるからです。」
「それ以外の場合は、ユーザに提示」あなたは実行時例外を使用する場合、私は意味怠惰なプログラマはそれ(空のcatchブロックでそれをキャッチ対)は無視されますと、ユーザはそれを見ることができます。
議論の要約の要約は、「プログラマーはそれらを適切に使用せず、適切に使用しないことはそれらを持たないことよりも悪い」ということです。
この議論にはいくつかの真実があり、実際、GoslingsのJavaでオペレーターによるオーバーライドを行わない動機は、同様の議論にあると考えています。
しかし、結局のところ、私はそれがHejlsbergの偽の議論であり、おそらく考え抜かれた決定ではなく、欠如を説明するために作成された事後的なものであることに気づきます。
チェックされた例外の過剰使用は悪いことであり、ユーザーによるずさんな処理につながる傾向がありますが、それらを適切に使用することで、APIプログラマーはAPIクライアントプログラマーに大きな利益をもたらすことができると私は主張します。
APIプログラマーは、チェックされた例外をあちこちに投げないように注意する必要があります。そうしないと、クライアントプログラマーを困らせるだけです。非常に怠惰なクライアントプログラマは(Exception) {}
、Hejlsbergが警告するようにキャッチすることに頼り、すべての利益が失われ、地獄が続くでしょう。ただし、状況によっては、適切なチェック例外の代わりになるものはありません。
私にとって、古典的な例はファイルを開くAPIです。言語の歴史にあるすべてのプログラミング言語(少なくともファイルシステム上)には、ファイルを開くためのAPIがどこかにあります。そして、このAPIを使用するすべてのクライアントプログラマーは、開こうとしているファイルが存在しない場合に対処する必要があることを知っています。つまり、このAPIを使用するすべてのクライアントプログラマは、このケースに対処する必要があることを知っている必要があります。そして、問題があります。APIプログラマーは、コメントだけで対処する必要があることをAPIプログラマーが支援できるか、またはクライアントが実際に対処するように主張できるかです。
Cでは、イディオムは次のようになります。
if (f = fopen("goodluckfindingthisfile")) { ... }
else { // file not found ...
where fopen
は0を返すことで失敗を示し、Cは(愚かに)0をブール値として扱うことができます。基本的に、このイディオムを学習すれば大丈夫です。しかし、あなたが初心者でイディオムを学んでいない場合はどうでしょう。そして、もちろん、あなたは
f = fopen("goodluckfindingthisfile");
f.read(); // BANG!
そして、難しい方法を学びます。
ここでは、厳密に型指定された言語についてのみ説明していることに注意してください。APIが厳密に型指定された言語であるという明確なアイデアがあります。それは、明確に定義されたプロトコルごとに使用する機能(メソッド)のほんの一部です。
その明確に定義されたプロトコルは、通常、メソッドシグネチャによって定義されます。ここで、fopenには文字列(またはCの場合はchar *)を渡す必要があります。それ以外のものを指定すると、コンパイル時エラーが発生します。プロトコルに従っていない-APIを適切に使用していない。
一部の(あいまいな)言語では、戻り値の型もプロトコルの一部です。fopen()
一部の言語で同等のものを変数に割り当てずに呼び出そうとすると、コンパイル時エラーも発生します(これはvoid関数でのみ実行できます)。
私が言おうとしている点は次のとおりです。静的に型付けされた言語では、APIプログラマーは、明らかな間違いを犯した場合にクライアントコードがコンパイルされないようにすることで、クライアントがAPIを適切に使用するように促します。
(Rubyのような動的に型付けされた言語では、フロートなど、何でもファイル名として渡すことができます-そしてコンパイルされます。メソッドの引数を制御するつもりがないのに、チェックされた例外でユーザーを煩わせるのはなぜですか。ここで行われる引数は、静的型付け言語にのみ適用されます。)
では、チェックされた例外はどうですか?
これが、ファイルを開くために使用できる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プログラマーの両方の制御の及ばないもののためのものであるべきです。これが、チェックされた例外を悪用しないための鍵だと思います。
ファイルを開くことは、ポイントをうまく説明していると思います。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
は大きな不便であり、プログラミングの不良につながり、回避する必要があります。しかし、違いを見分ける方法。
私はそれが正確な科学ではないと思います、そして私はおそらく、ヘイルスバーグの議論の根底にあり、おそらくある程度正当化するでしょう。しかし、私はここでお風呂で赤ちゃんを捨てるのは幸せではないので、ここでいくつかのルールを抽出して、良好なチェック例外と不良を区別します。
クライアントの制御不能またはクローズとオープンの違い:
チェックされた例外は、エラーがAPI とクライアントプログラマの両方から制御できない場合にのみ使用してください。これは、システムがどの程度開いているか、または閉じているかに関係しています。クライアントプログラマーが、たとえばテーブルビュー(クローズドシステム)から行を追加および削除するすべてのボタン、キーボードコマンドなどを制御できる制約付き UI では、データをフェッチしようとすると、クライアントプログラミングのバグになります。存在しない行。任意の数のユーザー/アプリケーションがファイルを追加および削除できるファイルベースのオペレーティングシステム(オープンシステム)では、クライアントが要求しているファイルがユーザーの知らないうちに削除された可能性があるため、対処することが期待されます。 。
ユビキタス:
チェック例外は、クライアントが頻繁に行うAPI呼び出しでは使用しないでください。頻繁にということは、クライアントコードの多くの場所からということです。そのため、クライアントコードは同じファイルを何度も開こうとはしませんが、テーブルビューはRowData
さまざまな方法でさまざまな場所に配置されます。特に、私は多くのコードを書くつもりです
if (model.getRowData().getCell(0).isEmpty())
そして毎回try / catchでラップしなければならないのは面倒です。
ユーザーへの通知:
チェックされた例外は、エンドユーザーに表示される有用なエラーメッセージを想像できる場合に使用してください。これが「それが起こったらどうしますか?」私が上で提起した質問。これは項目1にも関連しています。クライアントAPIシステムの外部の何かが原因でファイルが存在しない可能性があることを予測できるため、ユーザーにそれについて合理的に伝えることができます。
"Error: could not find the file 'goodluckfindingthisfile'"
不正な行番号は内部のバグが原因であり、ユーザーの過失がないため、実際に提供できる有用な情報はありません。アプリがランタイム例外をコンソールに渡さない場合、おそらく次のような醜いメッセージが表示されます。
"Internal error occured: IllegalArgumentException in ...."
つまり、クライアントプログラマーがユーザーに役立つ方法で例外を説明できないと思われる場合は、おそらくチェック例外を使用しないでください。
だから、それらは私のルールです。やや工夫されており、間違いなく例外があります(可能であれば、私がそれらを調整するのを手伝ってください)。しかし、私の主な議論はFileNotFoundException
、checked例外がパラメーター型と同じくらい重要でAPIコントラクトの一部として役立つような場合があるということです。それで、それが誤用されているからといってそれを省略すべきではありません。
申し訳ありませんが、これをそれほど長くて気まぐれにするという意味ではありませんでした。2つの提案で終わります。
A:APIプログラマー:チェックされた例外は、有用性を維持するために控えめに使用してください。疑わしい場合は、チェックされていない例外を使用してください。
B:クライアントプログラマー:開発の早い段階で、ラップされた例外(google it)を作成する習慣をつけます。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) */}