厄介な例外をスローしないようにする方法は?


21

例外に関する Eric Lippertの記事を読むことは、プロデューサーとしてもコンシューマとしても、例外にどのように取り組むべきかについて、間違いなく目を見張るものでした。ただし、厄介な例外のスローを回避する方法に関するガイドラインを定義するのにまだ苦労しています。

具体的には:

  • a)他の誰かがあなたの前にレコードを変更したか、b)作成しようとしている値が既に存在するため、失敗する可能性のあるSaveメソッドがあるとします。これらの条件は例外ではなく予期されるものであるため、例外をスローする代わりに、メソッドのTryバージョン、TrySaveを作成することにします。TrySaveは、保存が成功したかどうかを示すブール値を返します。しかし、それが失敗した場合、消費者はどのように問題があったのかを知ることができますか?または、結果を示す列挙型、Ok / RecordAlreadyModified / ValueAlreadyExistsなどを返すのが最善でしょうか?integer.TryParseでは、メソッドが失敗する理由は1つしかないため、この問題は存在しません。
  • 前の例は本当に厄介な状況ですか?または、この場合に例外をスローするのが好ましい方法でしょうか?Entityフレームワークを含むほとんどのライブラリとフレームワークでそれがどのように行われるかを知っています。
  • メソッドのTryバージョンを作成するタイミングと、メソッドが機能するかどうかを事前にテストする方法を提供するタイミングをどのように決定しますか?私は現在これらのガイドラインに従っています:
    • 競合状態の可能性がある場合は、Tryバージョンを作成します。これにより、消費者が外因性の例外をキャッチする必要がなくなります。たとえば、前に説明したSaveメソッド。
    • 条件をテストするメソッドが元のメソッドが行うことをほぼすべて実行する場合は、Tryバージョンを作成します。たとえば、integer.TryParse()。
    • それ以外の場合は、条件をテストするメソッドを作成します。

1
失敗する可能性のある保存の例は、本当にひどく厄介な例外ではありません。それはごく普通のことで、おそらく単なる例外であるべきです。
S.Lott

@ S.Lott:それはごく普通のことですか?状況自体、またはこの状況で例外をスローしますか?とにかく、これが実際に厄介な状況であるかどうかは明らかではないということに同意します。質問を更新します。
マイク

「状況自体、またはこの状況で例外をスローする」両方。
-S.Lott

回答:


24

a)他の誰かがあなたの前にレコードを変更したか、b)作成しようとしている値が既に存在するため、失敗する可能性のあるSaveメソッドがあるとします。これらの条件は予期されるものであり、例外ではないため、例外をスローする代わりに、メソッドのTryバージョン、TrySaveを作成し、保存が成功したかどうかを示すブール値を返します。しかし、それが失敗した場合、消費者は問題が何であったかをどのように知るのでしょうか?

良い質問。

私の頭に浮かぶ最初の質問は、データが既にそこにある場合、どのような意味で保存が失敗したのですか?成功したように思えます。しかし、議論のために、実際には操作が失敗する多くの異なる理由があると仮定しましょう。

私の頭に浮かぶ2番目の質問は、ユーザーに返還したい情報は実用的ですか? つまり、彼らはその情報に基づいて何らかの決定を下すのでしょうか?

「エンジンチェック」ライトが点灯したら、ボンネットを開け、車に火がついていないエンジンがあることを確認し、ガレージに持ち込みます。ガレージにはもちろん、チェックエンジンライトが点灯している理由を伝えるあらゆる種類の特殊な診断装置がありますが、私の観点からすると、警告システムはうまく設計されています。酸素センサーが燃焼室内の異常なレベルの酸素を記録しているのか、アイドル速度検出器が取り外されているのか、その他の問題なのかは気にしません。同じアクションを実行ます。つまり、他の誰かにこれを理解させます

呼び出し側は保存が失敗した理由を気にしますか あきらめるか再試行する以外に、彼らはそれについて何かするつもりですか?

議論のために、呼び出し側は実際に操作が失敗した理由に応じて異なるアクションをとると仮定します。

頭に浮かぶ3番目の質問は、故障モードは例外的ですか?私はあなたが混乱するかもしれないと思うことも可能例外なく。ふつうの状況ではなく、例外ではあるが可能性のある状況として、2人のユーザーが同じレコードを同時に変更しようとしていると思います。

議論のために、例外的ではないと仮定しましょう。

頭に浮かぶ4番目の質問は、前もって悪い状況を確実に検出する方法があるかどうかです。

悪い状況が私の「外因性」バケツにある場合、いいえ。「他のユーザーがこのレコードを変更したか」と確実に言う方法はありません。あなたが質問した後にそれ変更するかもしれないからです。答えは、作成されるとすぐに古くなってしまいます。

頭に浮かぶ5番目の質問は、悪い状況を防ぐことができるようにAPIを設計する方法はありますか?

たとえば、「保存」操作に2つのステップが必要になるようにすることができます。ステップ1:変更中のレコードのロックを取得します。その操作は成功または失敗するため、ブール値を返すことができます。呼び出し元は、障害に対処する方法に関するポリシーを持つことができます。しばらく待ってから、もう一度試して、あきらめてください。ステップ2:ロックが取得されたら、保存してロックを解除します。現在、保存は常に成功するため、あらゆる種類のエラー処理を心配する必要はありません。保存が失敗した場合、それは本当に例外的です。


すべての非常に良い点、ありがとう。ここに、おそらく私の投稿を要約した修辞的な質問があります:File.Open()を再設計する場合、代わりにFile.TryOpen()を作成しますか?失敗の理由を消費者にどのように伝えますか?または、ここで外因性例外をスローすることが本当に最善の妥協点ですか?
マイク

10
@Mike:ファイルシステムは、外因性例外の使用の良い例です。それらはめったに失敗しないので、失敗は例外的です。それらは予測不能に失敗し、呼び出し元の完全に制御できない理由により(イーサネットケーブルを接続したままにすることができる「ロック」はありません)、失敗は多様であり、実行可能です(つまり、ファイルは見つかりませんvsファイルは見つかりましたが、書き込みアクセス権を持っていない場合は、さまざまな方法で対処できます。)これらはすべて、失敗を例外として表す理由です。
エリックリッパー

私は今質問に答えられると思いますが、私はただ興味があります;)メソッドが2つ以上の理由で失敗する可能性がある場合、失敗はアクション可能であり、失敗は例外的ではなく、失敗は事前に検出することも防止することもできません、あなたならどうしますか?
マイク

@マイクエリックの代弁はできませんが、それはエラーコードの良い場所のように聞こえます。おそらく、enumのメンバーを返します。
マシューリード

1

あなたの例では、ValueAlreadyExistsの状況を簡単に確認でき、確認する必要があり、保存を試みる前に例外が発生する可能性があります。この状況ではTryが必要だとは思いません。競合状態は事前に確認するのが難しいため、この場合、Save in a Tryをラップすることはおそらく非常に良い考えです。

一般的に、可能性が非常に高いと思われる条件(NoDataReturned、DivideByZeroなど)がある場合、またはチェックが非常に簡単(空のコレクションまたはNULL値など)である場合、チェックを試みます。例外をキャッチしなければならないポイントに到達する前に、事前にそれを処理します。これらの条件を事前に知ることは必ずしも容易ではないことを認めます。コードが厳密なテストを受けている場合にのみ現れることがあります。


0

save()メソッドは例外をスローする必要があります。

最上位層は、コマンドラインプログラムのようなUnixである場合を除き、プログラムを終了せずにユーザーをキャッチして通知する必要があります。

戻り値は、例外を管理するための良い方法ではありません。

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