Try / Catchを論理演算子として使用するかどうかの引数[非公開]


36

Try-Catchブロックを論理演算子として使用する会社のアプリで素敵なコードを発見しました。
つまり、「何らかのコードを実行し、このエラーがスローされる場合、このコードを実行しますが、それがこのエラーをスローする場合、代わりにこの3番目の処理を実行します」。
表示される「else」ステートメントとして「finally」を使用します。
私はこれが本質的に間違っていることを知っていますが、戦いを始める前に、よく考えられた議論を望んでいました。
また、この方法でTry-Catchを使用するための引数がある場合は、教えてください。

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


13
私はこの慣習に反対する議論しかありません、そして、あなたはすでにそれが悪い考えであると確信しているように見えるので、私はそれらをわざわざ投稿するつもりはありません。
FrustratedWithFormsDesigner

15
@FrustratedWIthFormsDesigner:それは悪い推論です。納得していない人はどうですか?私はそれが間違っていると知っているだけで「なぜ」とは本当に言えないので、具体的に理由を尋ねる私の実際の質問はどうですか。
ジェームズP.ライト

3
私の意見は、問題のコードが実際に何をするかに大きく依存しています。事前にチェックできないものがあります(または、多くのファイル操作など、しようとするときに競合状態を許可します-チェックと操作の間の遅延では、ファイルに何が起こる可能性があります)try。一般に例外を保証する例外的なケースがすべて、この特定のケースで致命的である必要はありません。それでは、例外を使用せずに、よりシンプルで、同等またはより堅牢な方法でそれを行うことができますか?

11
「finally」ブロックは、例外のスローに関係なく、常に前のコードの後に​​実行されるため、「finally」ブロックを「else」として実際に使用しないことを願っています。
11

2
彼らはどの言語を使用していますか?たとえば、標準ライブラリが定期的に例外をスローするOCamlでは、問題ありません。例外処理は非常に安価です。ただし、CLIまたはJVMではそれほど効率的ではありません。
SKロジック

回答:


50

例外処理は、フロー制御を処理するための高価な方法になる傾向があります(C#およびJavaの場合は特に)。

例外オブジェクトが構築されると、ランタイムは非常に多くの作業を行います-スタックトレースを取得し、例外が処理される場所を特定します。

フロー制御にフロー制御ステートメントを使用する場合、これらすべてのメモリとCPUリソースのコストは、拡張する必要はありません。

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

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


1
パフォーマンスについての良い点ですが、これはプラットフォームの詳細にも依存します。
FrustratedWithFormsDesigner

4
それが唯一の欠点である場合、よく感謝します-より良いコードを購入したら、私はいつでもパフォーマンスを放棄します(パフォーマンスクリティカルなパスを除きます;しかし、幸運なことに、一般的にパフォーマンスのあるアプリケーションでも、すべてのコードのわずか20%です-クリティカル)。

5
@delnan-いいえ、唯一の欠点ではありません。
-Oded

2
未発言という言葉以外、私はあなたの投稿に同意します。コードに例外が発生する場合があります。それらのほとんどは、DBまたはサービスコールとの接続障害のように、またはアンマネージコードを扱っているときはいつでも、予測可能でなければなりません。制御できない問題を見つけて対処します。ただし、コード内で回避できる例外に基づいたフロー制御は絶対にしないでください。
-SoylentGray

4
「例外処理は、フロー制御を処理する高価な方法になる傾向があります」。Pythonの場合はfalse。
-S.ロット

17

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

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

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

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

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

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


4
これは実際にはかなり良い議論だと思います。コードは簡潔で簡潔であり、コードの実行内容に意味があります。これは実際に私が見ているコードで起こっていることに似ていますが、それははるかに複雑で、ネストされており、30行以上のコードにまたがっています。
ジェームズP.ライト

@James:いくつかの小さなメソッドを抽出した場合、コードは問題ないようです。
ケビンクライン

1
Javaは、C#のTryParseのようなものを実際に使用できると主張できます。C#では、そのコードはになりますint i; if(long.TryParse(s, out i)) return i; else return null;。文字列を解析できなかった場合、TryParseはfalseを返します。解析できた場合、出力引数を解析結果に設定し、trueを返します。例外は発生せず(TryParseの内部でも)、特に.NETがFormatException常に示すようなプログラマーエラーを意味する型の例外は発生しません。
ケビンキャスカート

1
@Kevin Cathcart、しかしなぜそれが良いのですか?NumberFormatExceptionをキャッチするコードは、TryParseの結果でnullをチェックするよりもずっとわかりやすいです。
ウィンストンイーバート

1
@ChrisMiskowiec、私はあなたがどれをより明確に見つけるかは、あなたが何に慣れているかということの問題だと思う。魔法の値をチェックするよりも、例外をキャッチする方がはるかに簡単です。しかし、それはすべて主観的です。客観的には、バグを隠すデフォルトではなく(何も起こらなかったように続行する)健全なデフォルト(プログラムをクラッシュさせる)があるため、例外を優先すべきだと思います。.NETのパフォーマンスが例外で劣ることは事実です。しかし、それは例外の性質ではなく、.NETの設計の結果です。.NETの場合は、そうする必要があります。しかし、原則として、例外は優れています。
ウィンストンイーバート

12

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

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

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

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

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

  • 不十分な設計決定この方法で構築されたプログラムは、ほとんどの場合、問題をエレガントに解決するのに簡単に対応できない心のフレームを奨励します。最終プログラムの複雑さは、プログラマーがその実行を完全に理解することを妨げ、高いコストで短期的な改善につながる決定を下すことを奨励します。


10

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

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

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

2
if / elseもジャンプと見なされます。違いは、その特定の場所でのみ発生するジャンプであり(読みやすくなっていること)、ラベルの名前を心配する必要がないことです(try / catchでも不要です) 、ただしgotoとの比較)。しかし、「ジャンプだから悪い」とだけ言うと、そうでない場合はif / elseが悪いとも言えます。
jsternberg

1
@jsternbarg:はい、if / elseは実際にマシンレベルでジャンプにコンパイルされますが、言語レベルではジャンプターゲットは次のステートメントですが、ループの場合は現在のステートメントです。私は、それはジャンプというよりも一歩だと主張します;)
back2dos

9

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

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

PHP + MySQLにテーブルが存在するかどうかを確認するよりも、クエリテーブルのような愚かなことをするほうが速い場合があります(これをベンチマークする質問は、実際に唯一の負の投票で受け入れられた答えです) 。

それにもかかわらず、いくつかの理由で例外の使用を制限する必要があります。

  1. ネストされた例外の偶然の飲み込み。これはメジャーです。他の誰かが処理しようとしている深くネストされた例外をキャッチした場合、仲間のプログラマー(おそらくあなたも!)
  2. テストは非自明になります。例外を含むコードブロックには、いくつかの問題があります。一方、ブール値は、理論的にはデバッグするのが面倒ですが、一般的にはそうではありません。try...catch制御フローの支持者は一般に(私の経験では)「tryブロックのコードを最小化する」という哲学に従っていないため、これは特に当てはまります。
  3. else ifブロックは許可されません。十分に言った(そして誰かが「異なる例外クラスを使用できる」と反論した場合、私の応答は「あなたの部屋に行って、あなたが言ったことを考えるまで出てこない」です)。
  4. 文法的に誤解を招く。 Exception、(try...catch制御フローの哲学に固執していないように)他の世界に、何かが不安定な(おそらく回復可能)状態に入ったことを意味します。不安定な状態は悪い状態であり、実際に回避可能な例外がある場合は、夜中ずっと私たちを維持する必要があります(実際にはうそをつきます、嘘はありません)。
  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()、コピーアンドペーストジョブになる可能性は低くなります(再び、個人的な経験)。(確かに、この特定の議論は、実際の哲学そのものよりも、私が出会った人々と関係があります)。

AFAIK#6はJavaでのみ問題であり、他のすべての最新の言語とは異なり、クロージャーもスタックベースのリソース管理もありません。例外の使用に固有の問題ではないことは確かです。
ケビンクライン

4と5は私にとって同じポイントのようです。3-6は、言語がpythonの場合は適用されません。1と2は潜在的な問題ですが、tryブロックのコードを最小化することで対処できると思います。
ウィンストンイーバート

1
@ウィンストン私は同意しません。1と2は主要な問題であり、コーディングの標準は改善されていますが、潜在的なエラーを単純に回避する方が良いのではないかと疑問に思っています。言語がPythonの場合、3が適用されます。4-5はPythonにも適用できますが、必須ではありません。4と5は非常に異なる点です。誤解を招くことは文体の違いとは異なります。誤解を招くコードは、車両を起動するトリガーに「停止ボタン」という名前を付けるようなことをしている可能性があります。一方、文体的には、Cのすべてのブロックのインデントを回避することにも似ています。
cwallenpoole

3)Pythonには例外用のelseブロックがあります。あなたが一般的に1と2のために取得したい問題を回避するため、大規模便利です
ウィンストン・エバート

1
明確にするために、例外を使用することは一般的なフロー制御メカニズムであると主張していません。どんなに小さな「失敗」モードでも例外をスローすることを推奨します。例外処理バージョンはよりクリーンで、戻り値チェックバージョンよりもバグを隠す可能性が低いと思います。そして理想的には、ネストされたエラーをキャッチするのを難しくする言語が欲しいです。
ウィンストンイーバート

4

私の主な論点は、論理にtry / catchを使用すると論理フローが壊れることです。非論理構造を介して「論理」に従うことは、(私にとって)直感に反し、混乱を招く。私は自分のロジックを「if Condition then A else B」と読むことに慣れています。「A catchを実行してからBを実行してください」と同じ文を読むのは奇妙に感じます。ステートメントAが単純な割り当てであり、conditionfalseがfalseである場合に例外を強制するための追加のコードが必要な場合はさらに奇妙になります(ifとにかく-statementが必要になります)。


2

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

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

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

...これらはすべて同様に可能性が高いと思います。丁寧に聞いてください。


1

Try Catchは、例外処理にのみ使用する必要があります。さらに、特定の例外処理。try catchは、予期される例外のみをキャッチする必要があります。そうでない場合、整形式ではありません。catch all tr​​y catchを使用する必要がある場合は、おそらく何か間違ったことをしている可能性があります。

編集:

理由:try catchを条件演算子として使用する場合、REAL例外をどのように考慮しますか?


2
OPは理由/主張を求めています。あなたは何も提供していません。
-Oded

「条件付き演算子としてtry catchを使用する場合、REAL例外をどのように考慮しますか?」- catch「実際ではない」例外をキャッチする句とは異なる句で、それらの実際の例外をキャッチすることで?

@delnan-ありがとう!あなたは私がやろうとしていたポイントを証明しました。あなたが私の理由とオーデッドの両方にあなたが言ったことに答えた人は、彼がちょうど頑固である理由を探していないので、私は話すのをやめるだろうし、私は頑固さで私の時間を無駄にしない。
AJC

ここにリストされているいくつかの議論の欠陥を指摘しているので、あなたは私をスタッズボーンと呼んでいますか?ここで述べた他のポイントに対して、私が何も言うことがないことに注意してください。私はフロー制御に例外を使用するのが好きではありません(詳細なしに非難することはありませんので、OPによる詳細化のための私の質問です)、私は単に悪魔の擁護者を演じています。

1
try-catchを条件演算子として使用しても、「実際の」例外をキャッチすることを妨げることはありません。
ウィンストンイーバート

1

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


1
しかし、なぜ?問題のポイントは、なぜ(またはなぜそうでないのか)例外がそのためだけに適しているということです。
ウィンストンイーバート

常に「例外的」とは限りません。たとえば、ハッシュテーブルでキーが見つからないということはそれほど例外ではありませんが、OCamlライブラリはそのような場合に例外を発生させる傾向があります。そしてそれは大丈夫です-例外処理はそこで非常に安価です。
SKロジック

1
-1:トートロジーに関する声明
米粉クッキー

1

例外を使用する理由:

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

例外を使用しない理由:

  1. 多くの言語は、例外を迅速に作成するようには設計されていません。
  2. スタックのいくつかのレイヤーを移動する例外は、その後に一貫性のない状態を残す可能性があります

最終的には:

目標は、何が起こっているかを伝えるコードを書くことです。例外は、コードの実行内容に応じて、それを支援/妨害する可能性があります。辞書参照のKeyErrorに対するPythonのtry / catchは(言語を知っている限り)完全に5関数レイヤー離れた同じKeyErrorのtry / catchは危険です。


0

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

そうしようと計画Aは、本プランAの死亡した場合、キャッチ使用、プランBとし、実行...そしてプランBは、失敗した場合、最終的にどのフィックスAまたはBで失敗したサービスのいずれか、またはページのどちらかだろう、プランCキックオフへ私。


0

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


「C ++の標準では、本当に例外的なイベントに対して例外を保存する必要があります。」これはナンセンスです。言語の発明者でさえあなたに同意しません
arayq2

-1

それは悪い習慣ですが、私は時々Pythonでそれをします。ファイルが存在するかどうかを確認する代わりに、ファイルを削除しようとします。例外がスローされた場合、私はキャッチし、それがそこになかったことを知って先へ進みます。<==(別のユーザーがファイルを所有している場合は必ずしも当てはまりませんが、ユーザーのホームディレクトリで実行するため、このような事態は発生しません)。

それでも、使いすぎると悪い習慣になります。


3
その例では、「跳躍する前に見る」の代わりに例外を使用することは、実際には良いアイデアです。それは、競合状態を回避します。ファイルの可用性(存在、アクセス許可、ロックされていない)のチェックが成功したからといって、後で-ほんの数個のオペコードであっても-ファイルができるようにアクセス(オープン、削除、移動、ロックなど)が成功する間に変更(削除、移動、アクセス許可の変更)する。

ファイルが存在しないという理由だけでPythonが例外をスローしている場合、delnanが説明しているように、悪い習慣を助長しているようです。
ヨハンソン

@delnan:本当ですが、そのような競合状態に陥る可能性が高い場合は、デザインを再考する必要があります。そのような場合、システムの懸念事項が明確に分離されていません。そのように物事を行う非常に良い理由がない限り、自分のアクセスを統一するか、適切なデータベースを使用して同じ永続データでトランザクションを実行しようとする複数のクライアントを処理する必要があります。
back2dos

1
これは悪い習慣ではありません。Pythonでの推奨スタイル。
ウィンストンイーバート

@ back2dos、データベース/ファイルシステムの他のユーザーなどの外部リソースを処理するときにそれを防ぐことができますか?
ウィンストンイーバート

-1

Appleが定義する方法が気に入っています。例外はプログラマーエラーと致命的なランタイムエラーのみです。それ以外の場合は、エラーコードを使用します。「これはプログラマーのエラーだったのか?」と思いながら、いつ使用するかを決めるのに役立ちます。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.