私はあなたと同じブルース・エッケルのインタビューを読んだと思います - そしてそれはいつも私を悩ませています. 実際、この議論は、.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 の主張の根底にあり、おそらくある程度正当化すると思います。しかし、私はここで赤ちゃんをお風呂の水で放り出すのは嬉しくないので、ここでいくつかのルールを抽出して、良いチェック例外と悪いチェック例外を区別できるようにします。
クライアントの管理外またはクローズド vs オープン:
チェック例外は、エラー ケースが 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
、チェックされた例外がパラメーターの型と同じくらい重要で 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) */}