私はPythonistas(私はPythonをあまり使用していないので知らない)または他の言語のプログラマーの怒りをこの答えで呼び出すかもしれませんが、私の意見では、ほとんどの機能は理想的に言えばブロックを持ってはいけませんcatch
。理由を示すために、これを80年代後半から90年代前半にTurbo Cで作業するときに行わなければならなかった種類の手動エラーコード伝播と対比させてください。
したがって、ユーザーがロードするイメージファイルを選択したことに応答して、イメージまたはそのようなものをロードする関数があり、これはCおよびアセンブリで記述されているとします。
低レベルの関数を一部省略しましたが、エラー処理に関してどのような責任を負うかに基づいて、色分けされたさまざまな関数のカテゴリを特定したことがわかります。
障害と回復のポイント
今では、「失敗の可能性のあるポイント」と呼ばれる機能のカテゴリ(throw
つまり、つまり)や「エラー回復とレポート」機能(catch
つまり、つまり)のカテゴリを記述するのは決して難しくありませんでした。
これらの機能は、常にメモリを割り当てることができないように、外部の障害に実行することができます関数は、単に返すことができるので、例外処理が利用可能であった正しく前に書き込みするのは簡単だったNULL
かを0
か、-1
またはこの効果にグローバルエラーコードか何かを設定します。そして、エラーリカバリ/レポートは常に簡単でした。コールスタックを下って、障害をリカバリして報告するのが理にかなっているポイントに到達したら、エラーコードやメッセージを受け取ってユーザーに報告するだけです。そして当然、この階層のリーフにある関数は、将来どのように変更されても決して失敗することはありません(Convert Pixel
)(少なくともエラー処理に関して)正しく記述することは非常に簡単です。
エラーの伝播
ただし、人為的エラーが発生しやすい退屈な関数はエラー伝播関数でした。エラー伝播関数は、直接失敗することはなく、階層のどこかで失敗する可能性のある関数を呼び出します。その時点で、Allocate Scanline
から失敗を処理しなければならないことがありmalloc
、その後にダウンエラーを返すConvert Scanlines
場合、Convert Scanlines
そのエラーをチェックし、それを伝承しなければならないDecompress Image
、そしてDecompress Image->Parse Image
、そしてParse Image->Load Image
、そしてLoad Image
エラーが最終的に報告されたユーザ・エンド・コマンドに。
これは多くの人間がミスを犯す場所です。エラーを適切に処理する際に、機能の階層全体がエラーをチェックして失敗するのに1人のエラー伝播者しか失敗しないからです。
さらに、エラーコードが関数によって返される場合、多くの関数がエラーコードを返すために戻り値を予約しなければならないため、成功時に関心のある値を返す能力がコードベースの90%などでほとんど失われます失敗。
人的エラーの削減:グローバルエラーコード
それでは、どうすればヒューマンエラーの可能性を減らすことができますか?ここでは、Cプログラマーの怒りを呼ぶこともありますが、私の意見をすぐに改善するには、OpenGLのようなグローバルエラーコードを使用しglGetError
ます。これにより、少なくとも成功時に関数が意味のある意味のある値を返すようになります。エラーコードがスレッドにローカライズされている場合、このスレッドセーフで効率的な方法があります。
また、関数でエラーが発生する場合もありますが、以前のエラーを発見したために関数が途中で戻る前に少し長く続けることは比較的無害です。これにより、すべての単一関数で行われた関数呼び出しの90%に対してエラーをチェックすることなく、このようなことが可能になるため、細心の注意を払わずに適切なエラー処理を行うことができます。
人的エラーの削減:例外処理
ただし、上記のソリューションでは、手動if error happened, return error
タイプのコードの行数が削減された場合でも、手動エラー伝播の制御フローの側面を処理するために非常に多くの関数が必要です。エラーをチェックし、ほとんどすべてのエラー伝播関数を返すために少なくとも1つの場所が必要になることが多いため、完全に排除することはできません。したがって、これは、例外処理が日を節約するために画像に登場するときです(ちょっと)。
ただし、ここでの例外処理の価値は、手動エラー伝播の制御フローの側面に対処する必要性を解放することです。つまり、その値はcatch
、コードベース全体に大量のブロックを書き込む必要を回避する能力に結びついています。上記の図では、catch
ブロックを配置する必要がある唯一の場所Load Image User Command
は、エラーが報告される場所です。catch
エラーコードの処理と同じくらい退屈でエラーが発生しやすくなるため、他に何もする必要はありません。
ですから、もしあなたが私に尋ねると、エレガントな方法で例外処理から本当に恩恵を受けるコードベースを持っているなら、それは最小数のcatch
ブロックを持っているべきです失敗する可能性のあるエンドユーザー操作、およびすべてのハイエンドユーザー操作が中央コマンドシステムを介して呼び出される場合はさらに少ない可能性があります)。
リソースのクリーンアップ
ただし、例外処理は、通常の実行フローとは別の例外パスでのエラー伝播の制御フローの側面を手動で処理する必要性を解決するだけです。多くの場合、エラー伝播関数として機能する関数は、EHで自動的にこれを行う場合でも、破壊する必要のあるリソースを取得する可能性があります。たとえば、このような関数は、関数から戻る前に閉じる必要のある一時ファイルを開いたり、ロックを解除する必要があるミューテックスをロックしたりします。
このために、あらゆる種類の言語から多くのプログラマーの怒りを呼ぶかもしれませんが、これに対するC ++のアプローチは理想的だと思います。この言語は、オブジェクトがスコープから出た瞬間に決定論的な方法で呼び出されるデストラクタを導入します。このため、たとえば、デストラクタでスコープ付きミューテックスオブジェクトを介してミューテックスをロックするC ++コードは、オブジェクトがスコープ外に出ると(例外が発生した場合でも)自動的にロック解除されるため、手動でロック解除する必要はありません遭遇します)。したがって、ローカルリソースのクリーンアップを処理する必要のある、適切に記述されたC ++コードは本当に必要ありません。
デストラクタのない言語では、finally
ブロックを使用してローカルリソースを手動でクリーンアップする必要があります。それはそれはまだ手動誤差伝搬とあなたのコードがゴミを有するビート、言ってあなたがする必要はありませんcatch
すべておかしく場所に例外。
外部副作用の逆転
これは、解決が最も難しい概念上の問題です。エラー伝播者または障害点のいずれかの機能が外部副作用を引き起こす場合、それらの副作用をロールバックまたは「元に戻す」必要があります。操作が途中で成功した「半有効」状態。不変性と永続的なデータ構造を中心とする関数型言語のように、そもそも外部の副作用を引き起こすほとんどの関数の必要性を単純に減らす言語を除いて、この概念上の問題をはるかに簡単にする言語はありません。
ここではfinally
、多くの場合、このタイプのロジックが特定の機能に非常に特異的で、「リソースのクリーンアップの概念に非常によくマッピングされていないので、間違いなくそこに可変性と副作用を中心言語の問題に最もエレガントなソリューションの中で「。そしてfinally
、catch
ブロックが必要かどうかに関係なく、関数がそれをサポートする言語の副作用を確実に反転させるために、これらの場合に寛大に使用することをお勧めします(そして、私に尋ねると、よく書かれたコードには最小限の数があるはずですcatch
ブロック。すべてのcatch
ブロックは、上の図のように最も意味のある場所に配置する必要がありますLoad Image User Command
。
夢の言語
ただし、IMO finally
は副作用の反転には理想的ですが、完全ではありません。次boolean
のように、(スローされた例外などからの)早期終了の場合に副作用を効果的にロールバックするために、1つの変数を導入する必要があります。
bool finished = false;
try
{
// Cause external side effects.
...
// Indicate that all the external side effects were
// made successfully.
finished = true;
}
finally
{
// If the function prematurely exited before finishing
// causing all of its side effects, whether as a result of
// an early 'return' statement or an exception, undo the
// side effects.
if (!finished)
{
// Undo side effects.
...
}
}
私が言語を設計することができたら、この問題を解決する私の夢の方法は、上記のコードを自動化する次のようなものです。
transaction
{
// Cause external side effects.
...
}
rollback
{
// This block is only executed if the above 'transaction'
// block didn't reach its end, either as a result of a premature
// 'return' or an exception.
// Undo side effects.
...
}
...デストラクタは、それは私たちが唯一の必要作り、ローカルリソースのクリーンアップを自動化するとtransaction
、rollback
とcatch
(私はまだ追加したいかもしれませんがfinally
自分自身をクリーンアップしていないCリソースでの作業、たとえば、のため)。ただし、変数finally
を使用するboolean
ことは、これまでに私の夢の言語が不足していることがわかりました。私はこのために見つけた二最も簡単な解決策はあるスコープガード C ++とDのような言語では、私はいつもそれが「リソースのクリーンアップ」と「副作用逆転」の考え方をぼかすため、スコープは、概念的には少しぎこちないガードしました。私の意見では、これらは異なる方法で取り組むべき非常に明確なアイデアです。
言語の私の小さな夢は、不変性と永続的なデータ構造を中心に大きく展開し、必要ではありませんが、大規模なデータ構造全体を深くコピーする必要がない効率的な関数を書くことをはるかに簡単にすることです副作用なし。
結論
とにかく、とりとめのtry/finally
ないところでは、Pythonにはデストラクタに相当するC ++がないことを考えると、ソケットを閉じるためのコードは素晴らしく、素晴らしいと思います。必要な場所の数を最小限に抑えてcatch
、最も意味のある場所にします。