エラー抑制に対する引数


35

私たちのプロジェクトの1つで、次のようなコードを見つけました。

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

私の知る限り、このようなエラーを抑制することは、元のサーバーの例外から有用な情報を破壊し、実際に終了する必要があるときにコードを継続させるため、悪い習慣です。

このようなすべてのエラーを完全に抑制するのが適切なのはいつですか?


13
Eric Lippertは、厄介な例外と呼ばれる素晴らしいブログ記事を書きました。そこでは、例外を4つの異なるカテゴリーに分類し、それぞれに適切なアプローチを提案しています。C#の観点から書かれていますが、言語間で適用可能だと思います。
-Damien_The_Unbeliever

3
コードは「フェイルファースト」の原則に違反していると思います。実際のバグが隠されている可能性が高いです。
陶酔


4
あなたは多くをキャッチしています。NRE、範囲外の配列インデックス、構成エラーなどのバグもキャッチします。これにより、それらのバグを見つけることができず、継続的に損傷を引き起こします。
usr

回答:


52

たくさんのライブラリを使用して、数千のファイルがあるコードを想像してください。それらがすべてこのようにコーディングされていると想像してください。

たとえば、サーバーの更新によって1つの構成ファイルが消える場合を想像してください。そして今あなたが持っているのは、そのクラスを使用しようとしたときにスタックトレースがnullポインタ例外であるということです:それをどのように解決しますか?数時間かかる場合があり、少なくとも[ファイルパス]が見つからないファイルの未加工のスタックトレースを記録するだけで、一時的に解決できる場合があります。

さらに悪いことには、更新後に使用するライブラリの1つで障害が発生し、後でコードがクラッシュする場合があります。これをどのようにライブラリに追跡できますか?

堅牢なエラー処理がなくても

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

または

LOGGER.error("[method name]/[arguments] should not be there")

時間を節約できます。

ただし、例外を無視して、このようにnullを返したい(または何もしない)場合があります。特に、適切に設計されていないレガシーコードと統合する場合、この例外は通常のケースとして予想されます。

実際、これを行うとき、あなたは本当にあなたが本当に例外を無視しているか、あなたのニーズのために「適切に処理している」かどうか疑問に思うべきです。その特定の場合にnullを返すことが例外を「適切に処理する」なら、それを行います。そして、これが適切なことである理由をコメントに追加します。

ベストプラクティスは、ほとんどの場合(おそらく80%、おそらく99%)に従うべきものですが、常に適用されないエッジケースが1つ見つかります。その場合、数か月後にコードを読む他の人(またはあなた自身)の慣習に従わない理由をコメントしてください。


7
コメントでこれを行う必要があると思う理由を説明するための+1。説明がなければ、例外嚥下は常に私の頭の中で警報を鳴らします。
jpmc26

私の問題は、コメントがそのコード内に残っていることです。関数の消費者として、私はそのコメントを見ることはないかもしれません。
corsiKa

@ jpmc26例外が処理される場合(たとえば、特別な値を返すことによって)、それはまったく例外を飲み込みません。
デデュプリケーター

2
@corsiKaは、関数が適切に機能している場合、実装の詳細を知る必要はありません。多分彼らはApacheライブラリのこの一部か、春であなたはそれを知らないでしょう。
ウォルフラット

@Deduplicatorはいしかし、あなたのアルゴリズムの一部があまりにも良いpraticeことになっていないとしてキャッチを使用して:)
Walfrat

14

このパターンが有用な場合がありますが、通常は、生成された例外が発生してはならない場合(つまり、例外が通常の動作に使用される場合)に使用されます。

たとえば、ファイルを開いて、返されるオブジェクトに保存するクラスがあるとします。ファイルが存在しない場合は、エラーではないと見なすことができます。nullを返し、代わりにユーザーに新しいファイルを作成させます。ファイルを開くメソッドは、ファイルが存在しないことを示すためにここで例外をスローする場合があるため、静かにキャッチすることは有効な状況です。

ただし、これを行うことはあまりよくありません。コードにこのようなパターンが散らばっている場合は、今すぐ対処する必要があります。少なくとも、このようなサイレントキャッチは、これが何が起こったのかをログに記録する(トレースが正しい)ことと、この動作を説明するコメントを書くことを期待しています。


2
「生成された例外が決してあるべきではないときに使用される」そのようなことはありません。例外のポイントは、何かがひどく間違っていたことを知らせることです。
陶酔

3
@Euphoricしかし、ライブラリの作成者は、そのライブラリのエンドユーザーの意図を知らない場合があります。ある人の「恐ろしい間違い」は、他の人の簡単に回復可能な状態かもしれません。
サイモンB

2
@Euphoric、それはあなたの言語が「恐ろしく間違っている」と考えるものに依存します:Pythonの人々は、C ++のフロー制御に非常に近いものの例外が好きです。nullを返すことは、状況によっては防御できる場合があります。たとえば、アイテムが突然かつ予測不能に追い出される可能性のあるキャッシュから読み取る場合です。
マットクラウス

10
@Euphoric、「ファイルが見つからないことは通常の例外ではありません。ファイルが存在しない可能性がある場合は、まずファイルが存在するかどうかを確認する必要があります。」いや!いや!いや!いや!それは、原子操作に関する古典的な間違った考え方です。ファイルは、存在するかどうかを確認してからアクセスするまでに削除される場合があります。このような状況を処理する唯一の正しい方法は、例外にアクセスし、例外が発生した場合に処理するnullことです。たとえば、選択した言語が提供できる最高の場合に戻ることです。
デビッドアルノ

3
@Euphoric:それでは、少なくとも2回ファイルにアクセスできないことを処理するコードを書いてください。これはDRYに違反し、また未実行のコードは壊れたコードです。
デデュプリケーター

7

これは100%のコンテキスト依存です。そのような関数の呼び出し元がエラーを表示またはログに記録する必要がある場合、それは明らかにナンセンスです。呼び出し元がすべてのエラーまたは例外メッセージを無視した場合、例外またはそのメッセージを返すことはあまり意味がありません。要件が理由を表示せずにコードのみを終了することである場合、呼び出し

   if(QueryServer(args)==null)
      TerminateProgram();

おそらく十分でしょう。これにより、ユーザーがエラーの原因を見つけるのが難しくなるかどうかを検討する必要があります。その場合、これはエラー処理の誤った形式です。ただし、呼び出し元のコードが次のようになっている場合

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

コードレビュー中に開発者と議論する必要があります。誰かが怠requirementsすぎて正しい要件を見つけられないという理由でこのような形式の非エラー処理を実装する場合、このコードは生産に入るべきではなく、コードの作者はそのような怠inessの可能性のある結果について教えられるべきです。


5

私がシステムのプログラミングと開発に費やしてきた年に、問題のパターンが有用であるとわかったのは2つの状況だけです(どちらの場合も、抑制にはスローされた例外のロギングも含まnullれていたため、プレーンキャッチとリターンを良いプラクティスとは考えていません)。

2つの状況は次のとおりです。

1.例外が例外状態と見なされなかったとき

これは、スローする可能性のあるデータに対して操作を行うときです。スローする可能性があることはわかっていますが、処理されたデータは必要ないので、アプリケーションを実行し続けます。あなたがそれらを受け取った場合、それは良いです、そうでない場合、それも良いです。

クラスのいくつかのオプション属性が頭に浮かぶかもしれません。

2.アプリケーションで既に使用されているインターフェイスを使用して、ライブラリの新しい(より良い、より速い?)実装を提供する場合

ある種の古いライブラリを使用するアプリケーションがあり、例外をスローしなかったがnullエラーで返されたと想像してください。したがって、このライブラリ用のアダプタを作成し、ライブラリの元のAPIをほとんどコピーし、アプリケーションでこの新しい(まだ非スロー)インターフェイスを使用し、nullチェックを自分で処理しています。

新しいバージョンのライブラリ、または同じ機能を提供する完全に異なるライブラリが提供されnullます。s を返す代わりに例外をスローし、それを使用する必要があります。

メインアプリケーションに例外を漏らしたくないので、作成したアダプタに例外を抑制して記録し、この新しい依存関係をラップします。


最初のケースは問題ではなく、コードの望ましい動作です。ただし、2番目の状況では、nullライブラリアダプターの戻り値が実際にエラーを意味する場合は、APIをリファクタリングして例外をスローし、チェックする代わりにキャッチするのnullが良い方法です(そして、通常、コードに関してはそうです)。

私は個人的に例外抑制を最初のケースにのみ使用しています。nullsの代わりに例外を使用してアプリケーションの残りの部分を機能させるための予算がなかった2番目の場合にのみ使用しました。


-1

プログラムは、処理方法がわかっている例外のみをキャッチし、プログラマが予期していなかった例外を処理する方法をおそらく知ることができないと言うのは論理的ですが、そのような主張は、多くの操作が失敗する可能性があるという事実を無視します副作用のないほぼ無限の数の方法、および多くの場合、そのような障害の大部分に対する適切な処理は同一です。失敗の正確な詳細は無関係であり、その結果、プログラマがそれらを予測したかどうかは問題ではありません。

たとえば、関数の目的がドキュメントファイルを適切なオブジェクトに読み込み、そのオブジェクトを表示する新しいドキュメントウィンドウを作成するか、ファイルを読み込めなかったことをユーザーに報告する場合、無効なドキュメントファイルはアプリケーションをクラッシュさせません。代わりに、問題を示すメッセージを表示しますが、何らかの理由でドキュメントを読み込もうとしてシステムの他の状態が破損しない限り、アプリケーションの残りの部分を正常に実行させます。 。

基本的に、例外の適切な処理は、例外がスローされた場所よりも例外のタイプに依存することが少なくありません。リソースが読み取り/書き込みロックによって保護されており、読み取り用のロックを取得したメソッド内で例外がスローされた場合、メソッドはリソースに対して何も実行できないため、適切な動作は一般にロックを解除することです。 。書き込みのためにロックを取得しているときに例外がスローされた場合、保護されたリソースが無効な状態にある可能性があるため、ロックを無効にする必要があります。ロックプリミティブに「無効化」状態がない場合は、そのような無効化を追跡するフラグを追加する必要があります。他のコードが保護されたオブジェクトを無効な状態で見る可能性があるため、ロックを無効化せずにリリースするのは悪いことです。ただし、ロックをぶら下げたままにしておくことはできません。適切な解決策。適切な解決策は、ロックを無効にして、取得の保留中または将来の試行が直ちに失敗するようにすることです。

無効化リソースが使用される前に破棄されることが判明した場合、アプリケーションを停止する必要はありません。無効化されたリソースがアプリケーションの継続的な動作にとって重要である場合、アプリケーションを停止する必要がありますが、リソースを無効化するとその可能性が高くなります。元の例外を受け取ったコードには、多くの場合、どの状況が当てはまるかを知る方法がありませんが、リソースを無効にすると、どちらの場合でも正しいアクションがとられることを保証できます。


2
例外の詳細を知らずに例外を処理するため、これは質問への回答に失敗します!=(一般的な)例外を静かに消費します。
-Taemyr

@Taemyr:関数の目的が「可能な場合はXを実行するか、そうでなければXを実行できないことを示す」ことであり、Xが不可能だった場合、多くの場合、どちらの関数もまた、呼び出し側は「Xを実行できませんでした」以外のことも気にしません。ポケモンの例外処理は、多くの場合、このような場合に物事を機能させる唯一の実用的な方法であり、予期しない例外によって破損するものを無効にすることについてコードが規律されている場合、一部の人々が行うほど悪いことである必要はありません。
supercat

まさに。すべてのメソッドには目的が必要です。呼び出すメソッドが例外をスローするかどうかにかかわらず、目的を完全に満たすことができる場合は、例外を飲み込み、それが何であれ、その仕事をするのが正しいことです。エラー処理とは、基本的に、エラー後にタスクを完了することです。それが重要なことであり、エラーが発生したことではありません。
jmoreno

@jmoreno:物事を難しくしているのは、多くの状況で多数の例外があり、その多くはプログラマーが特に予期しないものであり、問​​題を示していないことと、いくつかの例外があります。問題を示すものではなく、それらを区別するための明確な体系的な方法はありません。私がそれを正気に扱う唯一の方法は、破損したものを例外で無効化することです。したがって、問題がある場合は気づき、問題がない場合は無視されます。
supercat

-2

この種のエラーを飲み込むことは、誰にとっても特に有用ではありません。はい、元の呼び出し元は例外を気にかけないかもしれませんが、他の誰かがそうするかもしれません

それから彼らは何をしますか?例外を処理するためのコードを追加して、どのようなフレーバーの例外を取得しているかを確認します。すばらしいです。ただし、例外ハンドラが残されている場合、元の呼び出し元はnullを返さなくなり、何かが他の場所で壊れます。

一部のアップストリームコードがエラーをスローしてnullを返す可能性があることを認識している場合でも、少なくとも呼び出しコードIMHOで例外を回避しようとしないことは非常に不活性です。


「真剣に不活性」-「真剣に不適当」という意味でしょうか?
難解なスクリーン名

@EsotericScreenName 1つを選択... ;
ロビーディー

-3

サードパーティのライブラリが有用なメソッドを持っている可能性のある例を見てきましたが、例外として私にとってはうまくいく場合もあります。私に何ができる?

  • 自分が必要とするものを正確に実装します。締め切りが難しい場合があります。
  • サードパーティのコードを変更します。ただし、アップグレードのたびにマージの競合を探す必要があります。
  • ラッパーメソッドを作成して、例外を通常のNULLポインター、ブール値のfalseなどに変換します。

たとえば、ライブラリメソッド

public foo findFoo() {...} 

最初のfooを返しますが、ない場合は例外をスローします。しかし、必要なのは、最初のfooまたはnullを返すメソッドです。だから私はこれを書きます:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

きちんとしません。しかし、時には実用的なソリューション。


1
「例外が例外を投げる」とはどういう意味ですか?
corsiKa

@corsiKa、私の頭に浮かぶ例は、リストまたは同様のデータ構造を検索するメソッドです。何も見つからない場合に失敗するか、null値を返しますか?別の例は、ブール値のfalseを返すのではなく、タイムアウトで失敗するwaitUntilメソッドです。
om
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.