web-dev-qa-db-ja.com

論理演算子としてのTry / Catchの使用に関する賛成または反対

企業のアプリで、論理演算子としてTry-Catchブロックを使用する素敵なコードを見つけました。
意味、「コードを実行します。このエラーがスローされる場合は、このコードを実行しますが、このエラーがスローされる場合は、代わりにこの3番目のことを実行してください」。
表示される「else」ステートメントとして「Finally」を使用します。
これは本質的に間違っていることを知っていますが、戦いを選ぶ前に、よく考えられた議論を期待していました。
そして、ねえ、この方法でTry-Catchを使用することについて議論がある場合は、教えてください。

疑問に思っている人は誰でも、言語はC#で、問題のコードは約30行以上あり、特定の例外を探していますが、すべての例外を処理していません。

38
James P. Wright

例外処理は、フロー制御を処理する高価方法になる傾向があります(確かにC#とJavaの場合)。

ランタイムは、例外オブジェクトが構築されるときにかなり多くの作業を行います-スタックトレースをまとめて、例外が処理される場所などを把握します。

フロー制御にフロー制御ステートメントが使用されている場合、拡張する必要のないメモリとCPUリソースにこのすべてのコストがかかります。

さらに、意味上の問題があります。例外は例外的な状況であり、通常のフロー制御ではありません。通常のプログラムフローとしてではなく、予期しない/例外的な状況を処理するには、例外処理を使用する必要があります。

これら2つとは別に、他の人がコードを読み取る問題があります。このような方法で例外を使用することは、ほとんどのプログラマーが期待することではないので、読みやすさとコードの理解のしやすさが損なわれます。 「例外」を見たとき、人は考える-何か悪いことが起こった、通常は起こらないはずの何か。したがって、この方法で例外を使用すると、混乱するだけです。

54
Oded

会社のアプリで、論理演算子としてTry-Catchブロックを使用する素敵なコードを見つけました。つまり、「コードを実行します。このエラーがスローされる場合は、このコードを実行しますが、このエラーがスローされる場合は、代わりに3番目のコードを実行します」。表示される「else」ステートメントとして「Finally」を使用します。これは本質的に間違っていることを知っています...

どうやってわかったの?私はそのような「知識」をすべてあきらめ、今では最も単純なコードが最良であると信じています。文字列を、解析が失敗した場合は空のOptionalに変換するとします。何も問題はありません:

try { 
    return Optional.of(Long.valueOf(s)); 
} catch (NumberFormatException) { 
    return Optional.empty(); 
}

「例外は例外的な状況にある」という通常の解釈には完全に同意しません。関数が使用可能な値を返せない場合、またはメソッドがその事後条件を満たさない場合は、例外をスローします。実証されたパフォーマンスの問題が発生するまで、これらの例外がスローされる頻度は重要ではありません。

例外は、エラー処理を通常のフローから分離できるようにすることで、コードを簡素化します。できるだけ単純なコードを記述し、try-catchを使用したり、例外をスローしたりする方が簡単な場合は、それを行ってください。

例外は、コードを通るパスの数を減らすことでテストを簡素化します。分岐のない関数は、完了するか、例外をスローします。エラーコードをチェックする複数のifステートメントを持つ関数には、多くの可能なパスがあります。条件の1つを誤って取得したり、条件を完全に忘れたりして、エラー条件が無視されるのは非常に簡単です。

16
kevin cline

例外を使用して制御フローを実行する場合、デバッグおよび保守作業は非常に困難です。

本質的に、例外はプログラムの通常の制御フローを変更するためのメカニズムとして設計されています-それほど複雑では処理できない特にタイトなバインドから抜け出す方法として、異常なアクティビティを実行し、異常な副作用を引き起こします手段。例外は例外です。つまり、作業している特定の環境に応じて、通常の制御フローに例外を使用すると、次のことが発生する可能性があります。

  • 非効率例外に必要な比較的困難なコンテキスト変更を安全に実行するために環境がジャンプしなければならない追加のフープには、計測とリソースが必要です。

  • デバッグの難しさ例外が発生すると、(プログラムをデバッグするときに)役立つ情報がウィンドウからスローされることがあります。ランタイム動作の理解に関連するプログラムの状態または履歴を追跡できなくなる可能性があります

  • メンテナンスの問題実行フローは例外ジャンプをたどることが困難です。それを超えると、ブラックボックス型のコードの内部から例外がスローされる場合があり、例外をスローするときに理解しにくい動作をする場合があります。

  • 貧弱な設計決定この方法で構築されたプログラムは、ほとんどの場合、問題を優雅に解決することに簡単に対応できない心構えを助長します。最終的なプログラムの複雑さは、プログラマーがその実行を完全に理解することを思いとどまらせ、長期的なコストの高い短期的な改善につながる決定を下すことを奨励します。

13
blueberryfields

このパターンが何度も使用されているのを見てきました。

2つの大きな問題があります。

  • これは非常にコストがかかります(例外オブジェクトのインスタンス化、呼び出しスタックの収集など)。一部のコンパイラは実際にそれを最適化できる可能性がありますが、例外はそのような使用を意図していないため、人々がそれを最適化することを期待できないため、この場合はそれを当てにしません。
  • 制御フローの例外の使用は、実際にはgotoによく似ています。ジャンプは、いくつかの理由で有害と見なされます。すべての選択肢にかなりの欠点がある場合は、それらを使用する必要があります。実際、すべてのコードで、ジャンプが明らかに最良の解決策であった2つのケースのみを思い出します。
10
back2dos

例外が最も速い場合があります。 Javaでも制御構造を使用するよりもnullオブジェクトの例外の方が速いケースを見ました(現時点では調査を引用できないので、あなたは私を信頼する必要があります)。問題Javaが実際に時間をかけてカスタム例外クラスのスタックトレースにデータを入力する必要がある場合)は、ネイティブクラス(少なくとも部分的にキャッシュされているように見える)を使用する代わりに発生します。一方的に高速または低速の場合は、ベンチマークに適しています。

Pythonでは、例外が発生する可能性のある処理を実行してからエラーを処理する方がはるかに正確です。はい、型システムを強制できますが、言語の哲学-代わりにメソッドを呼び出して結果をキャッチするだけです!(ファイルが書き込み可能かどうかのテストも同様です-ファイルに書き込んでエラーをキャッチしてください)。

PHP + MySQLにテーブルが存在するかどうかを確認するよりも、クエリテーブルのような愚かなことをするほうが速いときを見てきました( question ここで、これは実際には私のベンチマークです)反対票でのみ受け入れられた回答)。

そうは言っても、いくつかの理由で例外の使用は制限されるべきです:

  1. ネストされた例外の偶発的な飲み込み。これは重大です。他の誰かが処理しようとしている深くネストされた例外をキャッチした場合、あなたは仲間のプログラマー(おそらくあなたも!)を足で撃っただけです。
  2. テストは自明ではなくなります。例外があるコードのブロックには、いくつかの問題の1つが含まれている可能性があります。一方、ブール値は、理論的にはデバッグが面倒な場合がありますが、一般的にはそうではありません。 _try...catch_制御フローの支持者は一般的に(私の経験では)、「tryブロックのコードを最小化する」という哲学に従っていないため、これは特に当てはまります。
  3. _else if_ブロックは許可されていません。十分に述べています(そして、誰かが「しかし、彼らは異なる例外クラスを使用する可能性があります」と反論した場合、私の応答は「部屋に行ってください」あなたが言ったことについて考えるまでは出てこない。」)
  4. 文法的に誤解を招く可能性があります。Exceptionは、(_try...catch_制御フローの哲学を順守していない場合のように)他の国々にとって、何かが不安定になったことを意味します(おそらく回復可能です)。状態。不安定な状態は[〜#〜] bad [〜#〜]です。実際には、回避可能な例外があります(実際にはうそをつき、嘘はつきません)。
  5. 一般的なコーディングスタイルに準拠していません。コードとUIの両方での私たちの仕事は、世界をできるだけ明確にすることです。 _try...catch_制御フローは、一般的にベストプラクティスと見なされているものに反します。これは、プロジェクトに新しい人がプロジェクトを習得するのに時間がかかることを意味します。つまり、工数が増加し、まったく利益がありません。
  6. これはしばしばコードの重複につながります。最後にブロックしますが、厳密には必要ではありませんが、中断されたtryブロックによって開いたままにされたすべてのぶら下がりポインタを解決する必要があります。だからブロックを試してみてください。つまり、try{ obj.openSomething(); /*something which causes exception*/ obj.doStuff(); obj.closeSomething();}catch(Exception e){obj.closeSomething();}を使用できます。より伝統的な_if...else_のシナリオでは、closeSomething()がコピーアンドペーストジョブである可能性が低くなります(これも個人的な経験です)。 (確かに、この特定の議論は、実際の哲学自体よりも、私が会った人々との関係が深いです)。
9
cwallenpoole

私の主な主張は、ロジックにtry/catchを使用すると、論理フローが壊れることです。非論理構造を通じて「論理」に従うことは(私にとって)直観に反し、混乱します。私は自分のロジックを「if条件の場合はA else B」と読むことに慣れています。 「AをキャッチしてBを実行する」と同じステートメントを読むのは変です。ステートメントAが単純な割り当てであり、conditionがfalseの場合に例外を強制するために追加のコードを必要とする場合(さらに、ifステートメントが必要になる可能性があります)とにかく)。

まあ、エチケットの問題ですが、「彼らと議論を始める」前に、あなたがそれを述べているように、「なぜこれらすべての異なる場所で例外処理を使用するのですか?」

つまり、いくつかの可能性があります。

  1. 彼らは無能です
  2. 彼らがしたことには完全に正当な理由があり、それは一見しては見えないかもしれません。
  3. 時にはそれは好みの問題であり、いくつかの複雑なプログラムの流れを単純化するかもしれません。
  4. まだ誰も変わらなかった過去の遺物であり、誰かがそれをすれば幸せになるだろう

...私はこれらのすべてが等しく可能性があると思います。だからちょうど彼らにうまく聞いてください。

2
dagnelies

例外を使用する理由:

  1. 例外的な状況をキャッチするのを忘れた場合、プログラムは終了し、スタックトレースはその理由を正確に伝えます。例外状況の戻り値を処理するのを忘れた場合、プログラムが正しくない動作を示す距離はわかりません。
  2. 戻り値の使用は、返すことができるセンチネル値がある場合にのみ機能します。すべての可能な戻り値がすでに有効である場合、何をしますか?
  3. 例外には、何が起こったかについての追加情報が含まれています。

例外を使用しない理由:

  1. 多くの言語は、例外を高速化するように設計されていません。
  2. スタックのいくつかの層を移動する例外は、ウェイクに一貫性のない状態を残す可能性があります

最終的には:

目標は、何が起こっているかを伝えるコードを書くことです。例外は、コードが何をしているかに応じて、それを助ける/妨げる可能性があります。 pythonでのtry/catchは完全に(言語を知っている限り)です)5つの関数層から離れた同じKeyErrorのtry/catchは危険です。

1
Winston Ewert

キャッチは、例外処理にのみ使用してください。より具体的には、特定の例外処理。トライキャッチは、予期される例外のみをキャッチする必要があります。そうでない場合、形式が正しくありません。キャッチオールを使用する必要がある場合は、キャッチを試してください。

編集:

理由:条件演算子としてtryキャッチを使用する場合、REAL例外をどのように説明しますか?

1
AJC

例外は、例外的なことが発生した場合です。プログラムは通常のワークフローに従って機能していますか?

1
jfrobishow

それは言語に依存し、おそらく使用されている実装に依存します。 C++の標準では、例外は本当に例外的なイベントのために保存する必要があります。一方、Pythonでは、ガイドラインの1つは " 許可よりも許しを求める方が簡単です "なので、try/exceptブロックをプログラムロジックが推奨されます。

0

特定の状況では、フロー制御としてtry-> catchを使用しています。プライマリロジックが失敗した何かに依存しているが、例外をスローして終了したくない場合...それが、try-> catchブロックの目的です。監視されていないサーバー側のUNIXスクリプトをたくさん書いていますが、かわいらしく失敗するよりも、失敗しないほうがはるかに重要です。

したがってtryプランAで、プランAが死亡した場合、catchを実行し、プランBで実行します...そしてプランBが失敗した場合、finallyを使用してプランCを開始します。プランCは、AまたはBで失敗したサービスの1つを修正するか、ページを表示します。

0
Satanicpuppy