try-finally(catchなし)とenum-state検証の使用


9

私はこの質問について、例外が発生した場所にできるだけ近いところで例外を処理する方法についてのアドバイスを読んでいます。

ベストプラクティスのジレンマは、try / catch / finallyを使用してenum (または値を表すint、エラーの場合は0、OKの場合は1、警告の場合は2など)を返す必要があるかどうかです。答えは常に正しいですか、それとも呼び出し側がそれを処理するように例外を通過させるべきですか?

私が集めることができるものから、場合によってはこれが異なるかもしれないので、元のアドバイスは奇妙に思えます。

たとえば、Webサービスでは、常に状態を返す必要があるため、例外はその場で処理する必要がありますが、http経由でデータを投稿/取得する関数内で、例外(たとえば404の場合)は、それを起動したものにパススルーするだけです。そうでない場合は、結果の品質(エラー:404)と結果自体を呼び出し側に通知する方法を作成する必要があります。

データを取得/ポストするヘルパー関数内で404例外をキャッチすることは可能ですが、そうする必要がありますか?smallintを使用してプログラムの状態を示し(もちろん、それらを適切に文書化し)、この情報を外部の健全性検証の目的(すべてok /エラー処理)に使用するのは私だけですか?

更新:メインの分類で致命的または致命的でない例外を予期していましたが、回答を害しないようにこれを含めたくありませんでした。質問が何であるかを明確にしましょう:例外をスローするのではなく、スローされる例外を処理します。望ましい結果とは:エラーを検出し、エラーからの回復を試みます。復旧できない場合は、最も意味のあるフィードバックを提供してください。

ここでも、http get / postの例での問題は、元の呼び出し元に何が起こったかを説明する新しいオブジェクトを提供する必要があるかということです。このヘルパーが使用しているライブラリにあった場合、操作のステータスコードを提供することを期待しますか、それともtry-catchブロックに含めますか?設計している場合、ステータスコードを提供するか、例外をスローして、代わりに上位レベルにステータスコード/メッセージに変換させますか?

あらすじ:例外を生成するのではなく、コードの一部がステータスコードと、コードが生成する結果を返す場合、どのように選択しましたか?


1
これは、使用されているエラー処理の形式を変更するエラーを処理するものではありません。404の場合は、処理できないため、通過させます。
ストーンメタル

「値を表すint、エラーの場合は0、OKの場合は1、警告の場合は2など」これは、カフスの例だと言ってください!OKを意味するために0を使用することは、間違いなく標準です...
anaximander '15年

回答:


15

例外は例外的な状況で使用する必要があります。例外をスローすると、基本的には「ここではこの条件を処理できません。コールスタックの上位にいる誰かがこれをキャッチして処理できますか?」

呼び出し元がその値を受け取り、それに対して意味のあることを行うことが明らかな場合は、値を返すことが望ましい場合があります。これは、例外のスローがパフォーマンスに影響する場合、つまり、タイトなループで発生する場合に特に当てはまります。例外のスローは、値を返すよりもはるかに時間がかかります(少なくとも2桁)。

プログラムロジックを実装するために例外を使用しないでください。つまり、何かを実行するために例外をスローしないでください。例外をスローして、実行できなかったことを示します。


返信をありがとう、それは最も有益ですが、私の焦点は例外の処理ではなく、例外のスローです。404例外を受け取ったらすぐにキャッチするべきですか、それともスタックの上位に移動させるべきですか?
ミハリスバゴス

あなたの編集は表示されますが、私の答えは変わりません。呼び出し元にとって意味がある場合は、例外をエラーコードに変換します。そうでない場合は、例外を処理します。スタックを上げます。またはそれをキャッチし、それを使用して(ログに書き込むなど)、再スローします。
ロバートハーベイ、

1
+1:合理的でバランスの取れた説明。例外は、例外的なケースを処理するために使用する必要があります。それ以外の場合、関数またはメソッドは意味のある値(列挙型またはオプション型)を返し、呼び出し元はそれを適切に処理する必要があります。たぶん、関数型プログラミングで人気のある3番目の選択肢、つまり継続について言及するかもしれません。
ジョルジオ

4

私がかつて読んだいくつかの良いアドバイスは、処理しているデータの状態を考慮して進行できないときに例外をスローしますが、例外をスローする可能性のあるメソッドがある場合は、可能であれば、データが実際にあるかどうかをアサートするメソッドも提供しますメソッドが呼び出される前に有効です。

たとえば、System.IO.File.OpenRead()は、指定されたファイルが存在しない場合にFileNotFoundExceptionをスローしますが、前に呼び出す必要があるファイルが存在するかどうかを示すブール値を返す.Exists()メソッドも提供しますOpenRead()を呼び出して、予期しない例外を回避します。

質問の「いつ例外を処理するべきか」の部分に答えるために、私はあなたが実際にそれに対して何かをすることができるところならどこでも言うでしょう。メソッドが呼び出すメソッドによってスローされた例外を処理できない場合は、キャッチしないでください。コールチェーンを、それを処理できるものに引き上げます。場合によっては、これは単にApplication.UnhandledExceptionをリッスンするロガーである可能性があります。


多くの人々、特にpythonプログラマーはEAFPを好みます。つまり、「許可を得るよりも許しを求める方が簡単です」
Mark Booth

1
.Exists()と同様に、例外の回避に関するコメントの+1。
codeoutloud

+1これはまだ良いアドバイスです。私が書いたり管理したりするコードでは、例外は「例外的」であり、例外は開発者が見ることを目的としたものの9/10倍です。もう1回、これは対処できない問題であり、ログに記録して、できる限り終了します。たとえば、慣例により、通常は真の偽の応答があり、標準的な運賃/処理の内部メッセージがあるため、一貫性は重要です。すべてに対して例外をスローするように見えるサードパーティのAPIは、呼び出し時に処理でき、標準の合意されたプロセスを使用して返されます。
Gavin Howden、2015年

3

try / catch / finallyを使用して列挙型(または値を表すint、エラーの場合は0、OKの場合は1、警告の場合は2など)を返します。これにより、回答が常に正しいものになります。

それはひどいデザインです。数値コードに変換して例外を「マスク」しないでください。適切で明確な例外として残します。

または、呼び出し側がそれを処理するように、例外を通過させる必要がありますか?

それが例外です。

できる限り発生場所に近い場所で対処する

普遍的な真実ではありません。時々それは良い考えです。また、あまり役に立たない場合もあります。


それはひどいデザインではありません。Microsoftは多くの場所、つまりデフォルトのasp.NETメンバーシッププロバイダーに実装しています。私はこの答えは会話には何も貢献する方法を見ることができないこと以外は
Mihalis Bagos

@MihalisBagos:私ができることは、Microsoftのアプローチがすべてのプログラミング言語に採用されているわけではないということです。例外のない言語では、値を返すことが不可欠です。Cは最も顕著な例です。例外のある言語では、エラーを示す「コード値」を返すのはひどい設計です。それはif、(時々)より単純な例外処理の代わりに(時々)扱いにくいステートメントにつながります。答え(「選択方法...」)は簡単です。 しないでください。これは会話に追加されると思います。ただし、この回答は無視してかまいません。
S.Lott、2012

私はあなたの意見が重要でないと言っているわけではありませんが、あなたの意見は発展していないと言っています。そのコメントを基に、例外を使用できるのは、私たちができるからといって、そうすべきだというのが理由だと思います。コードフローケースの分類/整理ではなく、ステータスコードをマスキングとして表示しない
Mihalis Bagos

1
例外はすでに分類/組織です。明確な例外を「通常」または「例外的でない」戻り値と簡単に混同される可能性がある戻り値で置き換えることには、価値がないと思います。戻り値は常に例外ではありませ。私の意見はあまり発展していない:それは非常に単純です。例外を数値コードに変換しないでください。これはすでに、完全に優れたデータオブジェクトであり、想像できるすべての優れたユースケースに対応しています。
S.Lott

3

S.Lottに同意します。例外をソースのできるだけ近くでキャッチすることは、状況によっては良い考えかもしれませんし、悪い考えかもしれません。例外を処理するための鍵は、例外について何かを実行できる場合にのみ例外をキャッチすることです。それらをキャッチして数値を呼び出し元の関数に返すのは、一般的に悪い設計です。あなたが回復できるまでそれらを浮かせておくだけです。

私は常に例外処理をアプリケーションロジックから一歩離れた場所と考えています。この例外がスローされた場合、アプリケーションが回復可能な状態になるまでに、コールスタックをどれだけ遡ってクロールする必要がありますか?多くの場合、回復するためにアプリケーション内で実行できることが何もない場合は、最上位レベルまでキャッチできず、例外をログに記録して、ジョブを失敗させ、完全にシャットダウンしようとする可能性があります。

例外処理をいつどのように設定するかについては、何をすべきかがわかるまでそれらをそのままにしておく以外に、絶対的な規則はありません。


1

例外は美しいものです。これにより、不必要なあいまいさに頼ることなく、実行時の問題を明確に説明できます。例外は型付け、サブタイプ化でき、タイプごとに処理できます。他の場所で処理するために渡すことができ、処理できない場合は、アプリケーションの上位層で処理できるように再レイズできます。また、難読化されたエラーコードを処理するために多くのクレイジーロジックを呼び出す必要なく、メソッドから自動的に戻ります。

コードで無効なデータに遭遇した直後に例外をスローする必要があります。スローされる可能性のある例外を処理するには、try..catch..finallyで他のメソッドの呼び出しをラップする必要があります。特定の例外に応答する方法がわからない場合は、再度スローして、上位層に次のことを示します。別の場所で処理する必要がある問題があります。

エラーコードの管理は非常に難しい場合があります。通常、コードに不必要な重複が多数発生したり、エラー状態を処理するための厄介なロジックが大量に発生したりします。ユーザーであるとエラーコードが発生するのはさらに悪いことです。コード自体は意味がなく、ユーザーにエラーのコンテキストを提供しないからです。一方、例外は、「値を入力し忘れた」、「無効な値を入力した、使用できる有効な範囲は次のとおりです...」、「わからない」など、ユーザーに役立つ情報を伝えることができます。何が起こったのかを知り、テクニカルサポートに連絡して、クラッシュしたことを伝え、次のスタックトレースを提供します...」

OPに対する私の質問は、なぜ地球上でエラーコードを返すのではなく例外を使用したくないのかということです。


0

すべての良い答え。また、例外をスローする代わりにエラーコードを返すと、呼び出し元のコードがより複雑になる可能性があることも付け加えておきます。メソッドAがメソッドBを呼び出し、メソッドCを呼び出し、Cがエラーを検出したとします。Cがエラーコードを返す場合、Bはそのエラーコードを処理できるかどうかを判断するロジックを持つ必要があります。それができない場合は、Aに返す必要があります。Aがエラーを処理できない場合は、どうしますか?例外を投げますか?この例では、Cが単に例外をスローし、Bが例外をキャッチしない場合、コードははるかにクリーンになります。そのため、追加のコードを必要とせずに自動的に異常終了し、Aは特定のタイプの例外をキャッチしながら、他のユーザーに呼び出しを継続させることができます。スタック。

これは、次のようにコーディングするための良いルールを思い起こさせます:

コードの行は黄金の弾丸のようなものです。あなたは仕事を成し遂げるためにできるだけ少ないものを使いたいです。


0

私は両方のソリューションを組み合わせて使用​​しました。検証関数ごとに、検証ステータス(エラーコード)を入力するレコードを渡します。関数の最後で、検証エラーが存在する場合は、例外をスローします。これにより、各フィールドで例外をスローせず、1回だけスローします。

また、データが無効な場合に実行を継続させたくないため、例外をスローすると実行が停止することも利用しました。

例えば

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

ブール値のみに依存している場合、私の関数を使用する開発者はこれを考慮に入れて書く必要があります。

if not Validate() then
  DoNotContinue();

しかし、彼は忘れて電話するだけValidate()かもしれません(私は彼がすべきではないことを知っていますが、おそらく彼はそうするかもしれません)。

したがって、上記のコードで2つの利点を得ました。

  1. 検証関数の唯一の例外。
  2. 例外は、キャッチされなくても、実行を停止し、テスト時に表示されます

0

ここには答えが1つありません。一種のHttpExceptionがないようなものです。

基盤となるHTTPライブラリが4xxまたは5xx応答を受け取ったときに例外をスローすることは、理にかなっています。前回HTTP仕様を調べたとき、これらはエラーでした。

その例外をスローする(または、ラップして再スローする)ことについては、それは本当にユースケースの問題だと思います。たとえば、APIから一部のデータを取得してアプリケーションに公開するラッパーを作成している場合、意味的には、存在しないリソースへのリクエストでHTTP 404を返すことにより、それをキャッチしてnullを返すようにすることができます。一方、406エラー(受け入れられない)は、何かが変更され、アプリがクラッシュし、燃焼し、助けを求めて叫んでいるはずなので、エラーをスローする価値があるかもしれません。

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