実際、「通常の」プログラマーが例外を使用することを考えない場合には、例外を使用することをお勧めします。たとえば、「ルール」を開始し、それが機能しないことを検出するパーサーでは、例外は正しいリカバリポイントにブローバックするための非常に良い方法です。(これは、再帰から抜け出すというあなたの提案とある程度似ています。)
「例外は後藤に勝るものはない」という古典的な反対意見がありますが、これは明らかに誤りです。Javaやその他のほとんどの現代言語では、ネストされた例外ハンドラーとfinally
ハンドラーを使用できるため、例外を介して制御が転送されると、適切に設計されたプログラムでクリーンアップなどを実行できます。実際、このように、例外はいくつかの点でリターンコードでは、すべてのリターンポイントにロジックを追加してリターンコードをテストしfinally
、ルーチンを終了する前に正しいロジック(おそらくいくつかのネストされた部分)を見つけて実行する必要があるためです。例外ハンドラーを使用すると、ネストされた例外ハンドラーを介して、これは合理的に自動化されます。
例外には、いくつかの「手荷物」が含まれます。たとえば、Javaのスタックトレースです。ただし、Javaの例外は実際には非常に効率的であるため(少なくとも他のいくつかの言語の実装と比較して)、例外をあまり頻繁に使用しない場合、パフォーマンスは大きな問題にはなりません。
[私は40年のプログラミング経験があり、70年代後半から例外を使用していることを付け加えておきます。1980年頃に独立して「発明された」try/catch / finally(BEGIN / ABEXIT / EXITと呼ばれる)。]
「違法な」余談:
これらの議論でしばしば見落とされるのは、コンピューティングにおける最大の問題は、コストや複雑さ、標準やパフォーマンスではなく、制御であるということだと思います。
「制御」とは、「制御フロー」、「制御言語」、「オペレーター制御」、または「制御」という用語が頻繁に使用されるその他のコンテキストを意味するものではありません。私は一種の「複雑さの制御」を意味しますが、それはそれだけではありません。それは「概念的な制御」です。
私たちは皆それをやり遂げました(少なくとも約6週間以上プログラミングをしている私たちの場合)-実際の構造や標準(私たちが習慣的に使用するものを除く)のない「単純な小さなプログラム」を書き始めました。 「シンプル」で「使い捨て」なので、複雑さを気にする必要はありません。しかし、その後、コンテキストに応じて、おそらく10分の1または100分の1のケースで、「単純な小さなプログラム」は怪物に成長します。
私たちはそれに対する「概念的制御」を失います。1つのバグを修正すると、さらに2つのバグが発生します。プログラムの制御とデータフローは不透明になります。それは私たちが完全に理解できない方法で動作します。
それでも、ほとんどの標準では、この「単純な小さなプログラム」はそれほど複雑ではありません。それほど多くのコード行はありません。(私たちは熟練したプログラマーなので)「適切な」数のサブルーチンに分割されている可能性が非常に高いです。複雑さの測定アルゴリズムを実行すると(まだ比較的小さく、「サブルーチン化」されているため)、特に複雑ではないとスコアリングされます。
最終的に、概念的な制御を維持することが、事実上すべてのソフトウェアツールと言語の背後にある原動力です。はい、アセンブラーやコンパイラーのようなものは私たちをより生産的にし、生産性は主張された原動力ですが、その生産性の向上の多くは、「無関係な」詳細で忙しくする必要がなく、代わりに私たちが望む概念に集中できるためです実装する。
概念制御の大きな進歩は、「外部サブルーチン」が存在し、環境からますます独立するようになるにつれて、コンピューティングの歴史の初期に発生し、サブルーチン開発者がサブルーチンの環境について多くを知る必要がない「関心の分離」を可能にしました。サブルーチンのユーザーは、サブルーチンの内部について多くを知る必要はありませんでした。
BEGIN / ENDと「{...}」の単純な開発は、「インライン」コードでさえ「そこに」と「ここに」の間の分離から利益を得ることができるので、同様の進歩を生み出しました。
私たちが当たり前と思っているツールや言語機能の多くは存在し、これまで以上に複雑なソフトウェア構造に対する知的制御を維持するのに役立つため、便利です。そして、新しいツールや機能の有用性は、それがこの知的制御にどのように役立つかによって、かなり正確に測定できます。
残りの最大の困難な領域がリソース管理である場合の1つ。ここでの「リソース」とは、プログラムの実行中に「作成」または「割り当て」られ、その後何らかの形式の割り当て解除が必要になる可能性のあるエンティティ(オブジェクト、開いているファイル、割り当てられたヒープなど)を意味します。「自動スタック」の発明は、ここでの最初のステップでした。変数は「スタック上」に割り当てられ、「割り当てられた」サブルーチンが終了すると自動的に削除されます。(これはかつては非常に物議を醸した概念であり、多くの「当局」は、パフォーマンスに影響を与えるため、この機能の使用を推奨していませんでした。)
しかし、ほとんどの(すべての?)言語では、この問題は依然として何らかの形で存在します。明示的なヒープを使用する言語では、「新しい」ものは何でも「削除」する必要があります。開いたファイルはなんらかの方法で閉じる必要があります。ロックを解除する必要があります。これらの問題のいくつかは、(たとえば、GCヒープを使用して)細かく調整したり、ペーパーオーバー(参照カウントまたは「子育て」)したりできますが、すべてを排除または非表示にする方法はありません。そして、単純なケースでこの問題を管理するのはかなり簡単です(たとえば、new
オブジェクト、それを使用するサブルーチンを呼び出してから、delete
それ)、実際の生活はめったにそれほど単純ではありません。十数かそこらの異なる呼び出しを行い、呼び出し間でリソースをいくらかランダムに割り当て、それらのリソースの「ライフタイム」を変えるメソッドがあることは珍しいことではありません。また、呼び出しの中には、制御フローを変更する結果を返す場合があり、場合によってはサブルーチンを終了させたり、サブルーチン本体のサブセットの周りにループを引き起こしたりすることがあります。このようなシナリオでリソースを解放する方法(正しいものをすべて解放し、間違ったものを解放しない)を知ることは困難であり、サブルーチンが時間の経過とともに変更されると(複雑なすべてのコードがそうであるように)、さらに複雑になります。
メカニズムの基本的な概念try/finally
(アスペクトを少し無視するcatch
)は、上記の問題にかなりうまく対処します(完全にはほど遠いですが、認めます)。管理する必要のある新しいリソースまたはリソースのグループごとに、プログラマーはtry/finally
ブロックを導入し、割り当て解除ロジックをfinally節に配置します。リソースが確実に解放されるという実際的な側面に加えて、このアプローチには、関連するリソースの「範囲」を明確に示し、「強制的に維持される」一種のドキュメントを提供するという利点があります。
このメカニズムがメカニズムと結合されているという事実はcatch
、通常の場合にリソースを管理するために使用されるのと同じメカニズムが「例外」の場合にそれらを管理するために使用されるため、少し偶然です。「例外」は(表面上は)まれであるため、メインラインほど十分にテストされることはなく、エラーケースの「概念化」は平均的には特に難しいため、そのまれなパスのロジックの量を最小限に抑えることが常に賢明です。プログラマー。
確かに、try/finally
いくつかの問題があります。それらの最初の1つは、ブロックが非常に深くネストされる可能性があるため、プログラム構造が明確になるのではなく不明瞭になる可能性があることです。しかし、これはdo
ループやif
ステートメントに共通する問題であり、言語設計者からのインスピレーションを得た洞察を待っています。より大きな問題はtry/finally
、catch
(さらに悪いことに、例外的な)手荷物があることです。これは、必然的に二級市民に追いやられることを意味します。(たとえば、finally
現在非推奨のJSB / RETメカニズムを超えて、Javaバイトコードの概念としてさえ存在しません。)
他のアプローチがあります。IBM iSeries(または「Systemi」または「IBMi」またはそれらが現在呼んでいるもの)には、関連するプログラムが戻った(または異常終了した)ときに実行される、呼び出しスタック内の特定の呼び出しレベルにクリーンアップ・ハンドラーを付加するという概念があります。 )。これは、現在の形式では不器用であり、Javaプログラムで必要とされる細かいレベルの制御にはあまり適していません。たとえば、潜在的な方向を示しています。
そしてもちろん、C ++言語ファミリ(Javaではない)には、リソースを表すクラスを自動変数としてインスタンス化し、オブジェクトデストラクタに変数のスコープからの終了時に「クリーンアップ」を提供させる機能があります。(このスキームは、基本的にtry / finalを使用していることに注意してください。)これは多くの点で優れたアプローチですが、一連の一般的な「クリーンアップ」クラスまたは異なるタイプごとの新しいクラスの定義が必要です。リソースの、テキスト的にかさばるが比較的無意味なクラス定義の潜在的な「クラウド」を作成します。(そして、私が言ったように、それは現在の形のJavaのオプションではありません。)
しかし、私は逸脱します。