try ...最終的にcatch節なしで使用するのはなぜですか?


79

プログラムの古典的な方法はtry ... catchです。tryなしで使用するのが適切なのはいつcatchですか?

Pythonでは、以下は正当であると思われ、意味をなします。

try:
  #do work
finally:
  #do something unconditional

ただし、コードはcatch何もしませんでした。同様に、Javaでは次のように考えることができます。

try {
    //for example try to get a database connection
}
finally {
  //closeConnection(connection)
}

見た目が良く、突然例外の種類などを心配する必要はありません。これが良い習慣である場合、いつ良い習慣ですか?あるいは、これが良い習慣ではない、または合法でない理由は何ですか?(ソースをコンパイルしませんでした。Javaの構文エラーの可能性があるため、質問しています。Pythonが確実にコンパイルされることを確認しました。)

私が遭遇した関連する問題はこれです:私は関数/メソッドを書き続け、最後に何かを返さなければなりません。ただし、到達してはならない場所にある可能性があり、戻りポイントである必要があります。したがって、上記の例外を処理したとしてもNULL、コード内の到達すべきでないポイント(多くの場合はメソッド/関数の終わり)で、空の文字列を返すか、空のままにします。私は常にコードを再構築し、そうする必要がないようにしていますreturn NULL。なぜなら、それは絶対に良い習慣ではないように見えるからです。


4
Javaでは、tryブロックの最後にreturnステートメントを配置してみませんか?
ケビンクライン

2
try..finallyとtry..catchの両方がtryキーワードを使用し、tryから始まることは別として、2つの完全に異なる構成要素であることが残念です。
ピーターB

3
try/catch「プログラムの古典的な方法」ではありません。それはだ、プログラムへの古典的なC ++方法 C ++が適切な試みは/ついにあなたはRAIIを含む醜いハックを使用して保証可逆状態の変更を実装する必要が意味し、構築しないため。しかし、まともなオブジェクト指向言語はtry / finallyを提供するため、この問題はありません。try / catchとは非常に異なる目的で使用されます。
メイソンウィーラー

3
外部接続リソースでよく見ます。例外が必要ですが、開いている接続などを残さないようにする必要があります。それをキャッチした場合は、とにかく次のレイヤーに再スローします。
リグ

4
@MasonWheeler Uいハック」してください、オブジェクトがそれ自身のクリーンアップを処理することの何が悪いのか説明してください。
ボールドリック

回答:


146

この時点で発生する可能性のある例外を処理できるかどうかによって異なります。

例外をローカルで処理できる場合は、できる限りエラーを処理することをお勧めします。

それらをローカルで処理できない場合try / finallyは、メソッドが成功したかどうかに関係なく実行する必要のあるコードがあると仮定すると、ブロックを持つだけで完全に合理的です。例えば、(からニールさんのコメント)、ストリームを開いた後、ロードする内部メソッドにそのストリームを渡すと、あなたが必要があると思い際の優れた例でtry { } finally { }、ストリームは関係なく、成功の閉じていることを保証するためにfinally節を使用しますか、読み取りの失敗。

ただし、アプリケーションを完全にクラッシュさせたい場合を除き、コードのどこかに例外ハンドラが必要です。それは、アプリケーションのアーキテクチャに応じて、そのハンドラがどこにあるかによって異なります。


8
「そして、発生した場所にできるだけ近い場所でエラーを処理する方が良いでしょう。」ええ、それは依存します。(いわば)ミッションを回復し、それでも完了できる場合は、もちろんそうです。できない場合は、例外を一番上まで行かせてください。何が起こったのかを処理するために(おそらく)ユーザーの介入が必要になります。
ウィル

13
@will-だからこそ、「できるだけ」というフレーズを使用しました。
ChrisF

8
良い答えですが、例を追加します:ストリームを開き、そのストリームをロードする内部メソッドに渡すことは、必要な場合の優れた例です。finally try { } finally { }句を利用して、成功/失敗。
ニール

なぜなら、時には一番上ができる限り近くにあるからです
ニュートピア

「try / finallyブロックを設定するだけで完全に合理的」と答えたのはまさにこの答えです。ありがとう@ChrisF
ニールエイズ

36

このfinallyブロックは、エラー状態(例外)が発生したかどうかに関係なく、常に実行する必要があるコードに使用されます。

finallyブロック内のコードは、ブロックがtry完了した後、キャッチされた例外が発生した場合、対応するcatchブロックが完了した後に実行されます。キャッチされない例外がtryor catchブロックで発生した場合でも、常に実行されます。

finallyブロックは、一般的に開かれた決算ファイル、ネットワーク接続などに使用されるtryブロック。その理由は、ファイルまたはネットワーク接続を使用する操作が成功したか失敗したかに関係なく、ファイルまたはネットワーク接続を閉じる必要があるためです。

finallyブロック自体が例外をスローしないように、ブロックで注意が必要です。たとえばnull、などのすべての変数を必ず確認してください。


8
+1:「クリーンアップする必要があります」という言い回しです。のほとんどの用途はtry-finallywithステートメントに置き換えることができます。
-S.ロット

12
さまざまな言語には、try/finallyコンストラクトに対して非常に便利な言語固有の拡張機能があります。C#がありusing、Pythonは持っているwithなど、
yfeldblum

5
@yfeldblum- usingとの間に微妙な相違点があります。オブジェクトのコンストラクターで例外が発生した場合try-finallyDisposeメソッドはusingブロックによって呼び出されないためIDisposableです。 try-finallyオブジェクトのコンストラクターが例外をスローした場合でも、コードを実行できます。
スコットホイットロック

5
@ScottWhitlock:それは良いことですか?何をしようとしていて、構築されていないオブジェクトのメソッドを呼び出しますか?それは何十億もの悪いことです。
-DeadMG


17

Java でtry ...最終的にcatch句のない(さらには慣用的な)が適切な例は、並行ユーティリティロックパッケージでのLockの使用です。

  • APIドキュメントでの説明と正当化の方法を次に示します(引用符内の太字のフォントは私のものです)。

    ...ブロック構造のロックがないため、同期されたメソッドとステートメントで発生するロックの自動解放が削除されます。ほとんどの場合、次のイディオムを使用する必要があります

     Lock l = ...;
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }
    

    異なるスコープでロックとロック解除が発生する場合、ロックが保持されている間に実行されるすべてのコードがtry-finallyまたはtry-catchによって保護され、必要に応じてロックが解除されるように注意する必要があります。


私は置くことができますl.lock()してみてください内部の?try{ l.lock(); }finally{l.unlock();}
-RMachnik

1
技術的にはできます。意味的にはあまり意味がないので、そこには入れませんでした。tryそれとは関係のない何かでそれを汚すなぜこのスニペットでは、リソースへのアクセスをラップすることを意図している
ブヨ

4
できますが、l.lock()失敗した場合、finallyブロックがブロックl.lock()内にあれば実行されtryます。gnatが示唆するようにそれを行うと、ロックが取得されfinallyたことがわかったときにのみブロックが実行されます。
Wtrmute

12

基本レベルでcatchfinally2つの関連するが異なる問題を解決します。

  • catch 呼び出したコードによって報告された問題を処理するために使用されます
  • finally 問題が発生したかどうかに関係なく、現在のコードが作成/変更したデータ/リソースをクリーンアップするために使用されます

だから、両方のは、問題(例外)に何らかの形で関連しているが、それはかなり彼らが共通して持っているすべてです。

重要な違いは、finallyブロック(リソースリークを回避するために)リソースが作成された同じメソッド内にある必要あり、コールスタックの異なるレベルに配置できないことです。

catchしかし、別の問題である:それのための正しい場所は、に依存し、あなたが実際に扱うことができる場所の例外を。あなたがそれについて何もできない場所で例外をキャッチすることには意味がありません。


2
Nitpick:"... finallyブロックは、リソースが作成されたのと同じメソッド内になければなりません..."。リソースリークがないことを確認する方が簡単なので、そのようにするのは確かに良い考えです。ただし、これは必要な前提条件ではありません。つまり、そのようにする必要はありません。リソースをfinally(静的または動的に)囲むtryステートメントで解放しても、100%の漏れ防止が可能です。
スティーブンC

6

@yfeldblumには正しい答えがあります。通常、catchステートメントのないtry-finallyは、適切な言語構造に置き換える必要があります。

C ++では、RAIIとコンストラクタ/デストラクタを使用しています。Pythonでは、withステートメントです。C#では、それはusingステートメントです。

初期化コードとファイナライズコードが2つの場所ではなく1つの場所(抽象化されたオブジェクト)にあるため、これらはほとんど常にエレガントです。


2
Javaでは、ARMブロック
MarkJ

2
常に実行可能なオプションではない、適切に設計されていないオブジェクト(たとえば、C#でIDisposableを適切に実装していないオブジェクト)があるとします。
mootinator

1
@mootinator:うまく設計されていないオブジェクトから継承して修正することはできませんか?
ニールG

2
またはカプセル化?ああ。もちろん、はい。
mootinator

4

多くの言語では、finallyステートメントはreturnステートメントの後にも実行されます。これは、次のようなことができることを意味します。

try {
  // Do processing
  return result;
} finally {
  // Release resources
}

メソッドが例外または通常のreturnステートメントで終了した方法に関係なく、リソースを解放します。

これが良いか悪いかは議論の余地がありますtry {} finally {}が、例外処理に限定されるとは限りません。


0

私は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、多くの場合、このタイプのロジックが特定の機能に非常に特異的で、「リソースのクリーンアップの概念に非常によくマッピングされていないので、間違いなくそこに可変性と副作用を中心言語の問題に最もエレガントなソリューションの中で「。そしてfinallycatchブロックが必要かどうかに関係なく、関数がそれをサポートする言語の副作用を確実に反転させるために、これらの場合に寛大に使用することをお勧めします(そして、私に尋ねると、よく書かれたコードには最小限の数があるはずです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.
    ...
}

...デストラクタは、それは私たちが唯一の必要作り、ローカルリソースのクリーンアップを自動化するとtransactionrollbackcatch(私はまだ追加したいかもしれませんがfinally自分自身をクリーンアップしていないCリソースでの作業、たとえば、のため)。ただし、変数finallyを使用するbooleanことは、これまでに私の夢の言語が不足していることがわかりました。私はこのために見つけた二最も簡単な解決策はあるスコープガード C ++とDのような言語では、私はいつもそれが「リソースのクリーンアップ」と「副作用逆転」の考え方をぼかすため、スコープは、概念的には少しぎこちないガードしました。私の意見では、これらは異なる方法で取り組むべき非常に明確なアイデアです。

言語の私の小さな夢は、不変性と永続的なデータ構造を中心に大きく展開し、必要ではありませんが、大規模なデータ構造全体を深くコピーする必要がない効率的な関数を書くことをはるかに簡単にすることです副作用なし。

結論

とにかく、とりとめのtry/finallyないところでは、Pythonにはデストラクタに相当するC ++がないことを考えると、ソケットを閉じるためのコードは素晴らしく、素晴らしいと思います。必要な場所の数を最小限に抑えてcatch、最も意味のある場所にします。


-66

エラー/例外をキャッチし、それらをきちんと処理することは、必須ではない場合でも強くお勧めします。

私がこれを言う理由は、すべての開発者が自分のアプリケーションの振る舞いを知り、取り組む必要があると信じているからです。さもないと、彼は仕事を適切に完了していません。try-finallyブロックがtry-catch-finallyブロックに取って代わる状況はありません。

簡単な例を挙げます。例外をキャッチせずにサーバーにファイルをアップロードするためのコードを書いたと仮定します。何らかの理由でアップロードが失敗した場合、クライアントは何が問題なのかを知ることはありません。ただし、例外をキャッチした場合は、何が問題で、ユーザーがどのように修正できるかを説明するきちんとしたエラーメッセージを表示できます。

黄金律:推測には時間がかかるため、常に例外をキャッチする


22
-1:Javaでは、リソースを解放するためにfinally節が必要になる場合があります(ファイルを閉じる、DB接続を解放するなど)。これは、例外を処理する機能とは無関係です。
ケビンクライン

@kevincline、彼は最終的に使用するかどうかを尋ねていません...彼が求めているのは、例外をキャッチする必要があるかどうかだけです....最も本質的な部分、我々はすべてのことを知って、なぜそれが使われています....
パンカジUpadhyay

63
@Pankaj:あなたの答えは、があるcatch場合は常に句が常に存在するべきであることを示唆していますtry。私を含む経験豊富な貢献者は、これは不十分なアドバイスだと信じています。あなたの推論には欠陥があります。try例外をキャッチできる場所は、を含むメソッドだけではありません。多くの場合、コード全体でcatch句を複製するのではなく、最上位レベルからcatchおよびレポート例外を許可するのが最も簡単で最適です。
ケビンクライン

@kevincline、私は他の部分の質問の認識が少し異なっていると信じています。質問は、「例外をキャッチするかどうか」という正確なものでした。例外の処理は、試行錯誤だけでなく、さまざまな方法で実行できます。しかし、それはOPの関心事ではありませんでした。例外を上げることがあなたと他の人にとって十分であるならば、私は幸運を言わなければなりません。
パンカジUpadhyay

例外を除いて、あなたがしたい、正常に中断されるステートメントの実行(および手動各ステップでの成功をチェックせずに)。これは、深くネストされたメソッド呼び出しの場合に特に当てはまります。一部のライブラリ内のメソッド4層では、例外を「飲み込む」ことはできません。すべてのレイヤーを介して破棄する必要があります。もちろん、各レイヤーは例外をラップして追加情報を追加できます。多くの場合、これはさまざまな理由で行われません。開発に時間がかかり、不必要な冗長性が最大の2つです。
ダニエルB
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.