質問

アプリケーションで例外を処理する方法を決めるのに困っています。

例外に関する問題の多くは、1) リモート サービスを介したデータへのアクセス、または 2) JSON オブジェクトの逆シリアル化に起因します。残念ながら、これらのタスク (ネットワーク接続の切断、制御できない不正な形式の JSON オブジェクト) のどちらも成功することは保証できません。

その結果、例外が発生した場合は、関数内でそれをキャッチし、呼び出し元に FALSE を返すだけです。私の論理では、呼び出し元が本当に気にしているのは、タスクが成功したかどうかであって、成功しなかった理由ではないということです。

典型的なメソッドのサンプル コード (JAVA) を次に示します)

public boolean doSomething(Object p_somthingToDoOn)
{
    boolean result = false;

    try{
        // if dirty object then clean
        doactualStuffOnObject(p_jsonObject);

        //assume success (no exception thrown)
        result = true;
    }
    catch(Exception Ex)
    {
        //don't care about exceptions
        Ex.printStackTrace();
    }
    return result;
}

このアプローチは良いと思いますが、例外を管理するためのベスト プラクティスが何であるかを知りたいと思っています (本当に例外をコール スタックまでバブルする必要がありますか?)。

主な質問を要約すると、次のようになります。

  1. 例外をキャッチするだけで、それをバブルアップしたり、(ログまたはユーザーへの通知を介して) 正式にシステムに通知したりするのは問題ありませんか?
  2. すべてに try/catch ブロックが必要になるわけではない例外に対するベスト プラクティスは何ですか?

フォローアップ/編集

すべてのフィードバックに感謝します。オンラインで例外管理に関する優れた情報源をいくつか見つけました。

例外管理はコンテキストに応じて異なるものの 1 つであるようです。しかし最も重要なことは、システム内の例外を管理する方法に一貫性を持たせることです。

さらに、過剰な try/catch によるコードの腐敗、または例外を考慮しないことにも注意してください (例外はシステムに警告していますが、他に何を警告する必要がありますか?)。

また、これはかなりの選択のコメントです m3rLinEz.

私は、Anders Hejlsbergに同意する傾向があり、あなたは、ほとんどの発信者が手術が成功したかどうかの場合にのみ気にすることです。

このコメントから、例外を処理するときに考慮すべきいくつかの疑問が生じます。

  • この例外がスローされる理由は何でしょうか?
  • それをどう扱うのが理にかなっているのでしょうか?
  • 呼び出し元は例外を本当に気にしているのでしょうか、それとも呼び出しが成功したかどうかだけを気にしているのでしょうか?
  • 呼び出し元に潜在的な例外の管理を強制することは適切ですか?
  • あなたはその言語の観念を尊重していますか?
    • 本当にブール値のような成功フラグを返す必要があるのでしょうか?ブール値 (または int) を返すのは、Java (Java では例外を処理するだけ) の考え方というよりも、C の考え方に近いものです。
    • 言語に関連付けられたエラー管理構造に従ってください:)!
役に立ちましたか?

解決

例外をキャッチしてエラーコードに変換したいのは奇妙なことです。後者がJavaとC#の両方でデフォルトである場合、呼び出し元は例外よりもエラーコードを好むと思いますか?

ご質問は:

  1. 実際に処理できる例外のみをキャッチする必要があります。ただ ほとんどの場合、例外をキャッチするのは正しいことではありません。 いくつかの例外があります(例:ロギングおよびマーシャリングの例外 スレッド間)しかし、それらの場合でも、一般的にすべきです 例外を再スローします。
  2. 間違いなく、多くのtry / catchステートメントを使用しないでください コード。繰り返しますが、アイデアは、処理できる例外のみをキャッチすることです。 最上位の例外ハンドラーを含めると、未処理の エンドユーザーにとっていくらか有用な何かへの例外 それ以外の場合は、すべての例外をキャッチしようとしないでください すべての可能な場所。

他のヒント

これは、アプリケーションと状況によって異なります。ライブラリコンポーネントを構築する場合は、例外をバブルする必要がありますが、例外はコンポーネントのコンテキストに合わせてラップする必要があります。たとえば、Xmlデータベースを構築していて、データを保存するためにファイルシステムを使用しており、データを保護するためにファイルシステムのアクセス許可を使用しているとします。 FileIOAccessDenied例外をバブルアップしたくないのは、実装がリークするためです。代わりに、例外をラップしてAccessDeniedエラーをスローします。これは、コンポーネントをサードパーティに配布する場合に特に当てはまります。

例外を飲み込むことが許されるかどうかについて。それはシステムに依存します。アプリケーションが失敗のケースを処理でき、失敗した理由をユーザーに通知してもメリットがない場合は、先に進みますが、失敗をログに記録することを強くお勧めします。問題のトラブルシューティングを支援し、例外を飲み込んでいる(または、内部例外を設定せずに代わりに新しい例外をスローする)のを助けるために、いらいらさせられることがいつもありました。

一般的には、次のルールを使用します。

  1. 私のコンポーネントでは&ライブラリ例外を処理するか、それに基づいて何かを行う場合にのみ、例外をキャッチします。または、例外で追加のコンテキスト情報を提供する場合。
  2. アプリケーションのエントリポイント、または可能な限り最高レベルで一般的なtry catchを使用します。ここで例外が発生した場合、ログに記録して失敗させます。理想は、例外がここに到達しないようにすることです。

次のコードはにおいがすることがわかりました:

try
{
    //do something
}
catch(Exception)
{
   throw;
}

このようなコードは意味がなく、含めるべきではありません。

このトピックに関する別の優れた情報源をお勧めします。これは、JavaのChecked Exceptionのトピックに関する、C#とJavaの発明者、Anders HejlsbergとJames Goslingのそれぞれとのインタビューです。

障害と例外

ページの下部にも優れたリソースがあります。

Anders Hejlsbergとあなたに同意する傾向があります。ほとんどの発信者は操作が成功したかどうかだけを気にします。

  

ビルベンダー:あなたは言及しました   スケーラビリティとバージョン管理の問題   チェックされた例外に関して。   あなたが何を意味するのか明確にできますか   これら2つの問題?

     

Anders Hejlsberg :始めましょう   バージョン管理。問題は   とても見やすいです。私と言ってみましょう   宣言するメソッドfooを作成します   例外A、B、およびCをスローします。   fooのバージョン2、追加したい   機能の束、そして今fooは   例外Dをスローします。   スローにDを追加するための変更   そのメソッドの句、なぜなら   そのメソッドの既存の呼び出し元は   ほとんど確実にそれを処理しない   例外。

     

スローに新しい例外を追加する   新しいバージョンの句がクライアントを破壊する   コード。メソッドをメソッドに追加するようなものです   インタフェース。公開した後   インターフェイス、それはすべての実用的です   不変の目的   それの実装には   追加したいメソッド   次のバージョン。作成する必要があります   代わりに新しいインターフェース。同様に   例外を除いて、あなたは   というまったく新しいメソッドを作成する   より多くの例外をスローするfoo2、または   あなたは例外Dをキャッチする必要があります   新しいfoo、およびDを   A、B、またはC。

     

Bill Venners :しかし、あなたは壊れていませんか?   とにかく、その場合のコード   チェックされていない言語で   例外? fooの新しいバージョンの場合   新しい例外をスローします   クライアントは取り扱いについて考える必要があります   だけで彼らのコードが壊れているわけではありません   彼らがそれを期待していなかったという事実   彼らがコードを書いたときの例外?

     

Anders Hejlsberg :いいえ、多くの場合   場合、人々は気にしません。彼らは   これらのいずれも処理しません   例外。最下層があります   メッセージ周辺の例外ハンドラー   ループ。そのハンドラーは   行ったことを示すダイアログを表示します   間違って続行します。プログラマー   tryを記述してコードを保護します   最終的にどこにでもあるので、彼らは戻ってきます   例外が発生した場合、正しく出力   しかし、彼らは実際には興味がありません   例外の処理。

     

throws句、少なくとも   Javaで実装されており、   必ずあなたに   例外ですが、処理しない場合   それら、あなたに認めさせる   正確にどの例外が通過するか   スルー。どちらかが必要です   宣言された例外をキャッチするか、それらを入れます   独自のthrows句で。働くために   この要件の周りに、人々は   ばかげたこと。例えば、彼らは   すべてのメソッドを次のように飾ります" throws   例外。"それは完全に   機能を無効にし、あなたが作ったばかり   プログラマーはもっとむさぼり書きます   ネバネバした。それは誰にも役に立たない。

編集:会話の詳細を追加

チェックされた例外は一般に議論のある問題であり、特にJavaで(後ほど、好意的で反対の人々のためにいくつかの例を見つけようとします)。

経験則として、例外処理はこれらのガイドラインに沿ったものであり、順不同です:

  • 保守性のために、常に例外をログに記録します。これにより、バグの表示を開始したときに、バグが発生した可能性のある場所を示すのに役立ちます。 printStackTrace()やそれに類するものを放置しないでください。ユーザーの1人が最終的にこれらのスタックトレースの1つを取得し、何をすべきかについてまったく知識がありません
  • 処理できる例外をキャッチし、それらだけをそれらを処理します。単にスタックにスローするだけではありません。
  • 常に特定の例外クラスをキャッチします。通常、タイプ Exception をキャッチしないでください。そうでなければ、重要な例外を飲み込む可能性が非常に高くなります。
  • Error sを決して(決して)キャッチしない、意味: Throwable s エラーは後者のサブクラスです。 Error は、ほとんどの場合処理できない問題です(例: OutOfMemory 、またはその他のJVMの問題)

特定のケースについては、メソッドを呼び出すクライアントが適切な戻り値を受け取ることを確認してください。何かが失敗した場合、ブール値を返すメソッドはfalseを返す可能性がありますが、そのメソッドを呼び出す場所がそれを処理できることを確認してください。

対処できる例外のみをキャッチする必要があります。たとえば、ネットワーク経由で読み取りを処理しているときに接続がタイムアウトになり、例外が発生した場合は、再試行できます。ただし、ネットワーク経由で読み取り中にIndexOutOfBounds例外が発生した場合、何が原因か分からないため(この場合は、文句を言わないので)本当に処理できません。 falseまたは-1またはnullを返す場合は、特定の例外用であることを確認してください。使用しているライブラリが、ヒープがメモリ不足である場合にスローされる例外が発生したときに、ネットワーク読み取りでfalseを返すことは望ましくありません。

例外は、通常のプログラム実行の一部ではないエラーです。プログラムの機能とその用途(ワードプロセッサと心臓モニタ)に応じて、例外が発生したときにさまざまなことをしたいと思うでしょう。私は通常の実行の一部として例外を使用するコードを扱ってきましたが、それは間違いなくコードのにおいです。

try
{
   sendMessage();

   if(message == success)
   {
       doStuff();
   }
   else if(message == failed)
   {
       throw;
   }
}
catch(Exception)
{
    logAndRecover();
}

このコードは私をbarさせます。 IMOは、重要なプログラムでない限り、例外から回復しないでください。例外をスローすると、悪いことが起こっています。

上記のすべては合理的であるように思われ、多くの場合、職場にはポリシーがあります。私たちの場所では、 SystemException (未チェック)および ApplicationException (チェック済み)の例外のタイプを定義しています。

SystemException が回復可能である可能性は低く、上部で一度処理されることに同意しました。さらにコンテキストを提供するために、 SystemException が拡張されて、発生場所を示します。 RepositoryException ServiceEception など

ApplicationException sは、 InsufficientFundsException のようなビジネス上の意味を持つ可能性があり、クライアントコードで処理する必要があります。

ウィトハットは具体的な例です。実装についてコメントするのは難しいですが、リターンコードは使用しません。メンテナンスの問題です。例外を飲み込む可能性がありますが、その理由を判断し、常にイベントとスタックトレースを記録する必要があります。最後に、メソッドには他の処理がないため、カプセル化を除いてかなり冗長です( doactualStuffOnObject(p_jsonObject); はブール値を返す可能性があります!

いくつかの考えとあなたのコードを見た後、あなたは単に例外をブール値として再スローしているように思えます。メソッドはこの例外を通過させ(キャッチする必要すらありません)、呼び出し元で処理することができます。それが重要な場所だからです。例外によって呼び出し元がこの関数を再試行する場合、呼び出し元が例外をキャッチする必要があります。

場合によっては、発生している例外が呼び出し側にとって意味をなさないことがあります(つまり、ネットワーク例外)。この場合、ドメイン固有の例外でラップする必要があります。

一方で、例外がプログラムで回復不可能なエラーを通知する場合(つまり、この例外の最終結果はプログラムの終了になります)私は個人的にそれをキャッチしてランタイム例外をスローすることで明示的にしたいです

サンプルでコードパターンを使用する場合は、TryDoSomethingを呼び出し、特定の例外のみをキャッチします。

また、 の使用を検討する診断目的で例外を記録する場合の例外フィルター 。 VBには、例外フィルターの言語サポートがあります。 Greggmのブログへのリンクには、C#から使用できる実装があります。例外フィルターには、キャッチと再スローよりもデバッグしやすいプロパティがあります。具体的には、問題をフィルターに記録し、例外の伝播を継続できます。このメソッドを使用すると、JIT(Just in Time)デバッガーをアタッチして、元のスタック全体を取得できます。再スローにより、スタックは再スローされた時点で切断されます。

TryXXXXが理にかなっているのは、本当に例外的ではない場合や、関数を呼び出さずにテストするのが簡単ではない場合にスローするサードパーティの関数をラップする場合です。例は次のようになります:

// throws NumberNotHexidecimalException
int ParseHexidecimal(string numberToParse); 

bool TryParseHexidecimal(string numberToParse, out int parsedInt)
{
     try
     {
         parsedInt = ParseHexidecimal(numberToParse);
         return true;
     }
     catch(NumberNotHexidecimalException ex)
     {
         parsedInt = 0;
         return false;
     }
     catch(Exception ex)
     {
         // Implement the error policy for unexpected exceptions:
         // log a callstack, assert if a debugger is attached etc.
         LogRetailAssert(ex);
         // rethrow the exception
         // The downside is that a JIT debugger will have the next
         // line as the place that threw the exception, rather than
         // the original location further down the stack.
         throw;
         // A better practice is to use an exception filter here.
         // see the link to Exception Filter Inject above
         // http://code.msdn.microsoft.com/ExceptionFilterInjct
     }
}

TryXXXのようなパターンを使用するかどうかは、よりスタイルの問題です。すべての例外をキャッチしてそれらを飲み込むという問題は、スタイルの問題ではありません。予期しない例外が伝播することを許可してください!

使用している言語の標準ライブラリからキューを取得することをお勧めします。 C#について話すことはできませんが、Javaを見てみましょう。

たとえば、java.lang.reflect.Arrayには静的な set メソッドがあります:

static void set(Object array, int index, Object value);

Cウェイは次のようになります

static int set(Object array, int index, Object value);

...戻り値は成功インジケータです。しかし、あなたはもうCの世界にいません。

例外を受け入れると、エラー処理コードをコアロジックから遠ざけることで、コードがより簡単で明確になることがわかります。単一の try ブロックに多くのステートメントを含めることを目指します。

他の人が指摘したように、キャッチする例外の種類はできるだけ具体的にする必要があります。

例外をキャッチして false を返す場合、それは非常に特殊な例外である必要があります。あなたはそうするのではなく、それらをすべてキャッチして false を返します。MyCarIsOnFireException が発生した場合は、すぐにそれについて知りたいと思います。残りの例外は気にしないかもしれません。したがって、一部の例外 (再スローするか、何が起こったのかをより適切に説明する新しい例外をキャッチして再スローする) については「おっと、ここで何かが間違っている」と通知し、その他の例外については false を返すだけの例外ハンドラーのスタックが必要です。

これがこれから発売する製品の場合は、これらの例外をどこかに記録する必要があります。これは、将来の調整に役立ちます。

編集:すべてを try/catch でラップするという質問については、答えは「はい」だと思います。コード内で例外が発生することは非常にまれであるため、catch ブロック内のコードが実行されることはほとんどなく、パフォーマンスにまったく影響を与えません。例外は、ステート マシンが壊れて何をすべきかわからない状態です。少なくとも、その時点で何が起こっていたのかを説明し、その中にキャッチされた例外を含む例外を再スローします。「メソッド doSomeStuff() の例外」は、休暇中 (または新しい仕事中) にエラーが発生した理由を解明する必要がある人にはあまり役に立ちません。

私の戦略:

元の関数が void を返した場合、 bool を返すように変更します。例外/エラーが発生した場合は false を返し、すべてが正常であれば true を返します。

関数が何かを返す必要がある場合、例外/エラーが発生したときに null を返し、そうでない場合は返却可能なアイテムを返します。

bool の代わりに、エラーの説明を含む string が返される可能性があります。

すべてのケースで、何かを返す前にエラーを記録します。

ここにいくつかの優れた答えがあります。あなたが投稿したようなものになった場合、少なくともスタックトレースよりも多くを印刷することを付け加えたいと思います。開発者に格闘のチャンスを与えるために、あなたが当時何をしていたか、そしてEx.getMessage()を言ってください。

try / catchブロックは、最初の(メイン)セットの上に埋め込まれた2番目のロジックセットを形成します。そのため、読み取り不能でデバッグが難しいスパゲッティコードを見つけるのに最適な方法です。

それでも、読みやすさの点で驚くほどうまく機能しますが、2つの簡単なルールに従う必要があります:

  • ライブラリ処理の問題をキャッチするために低レベルで(わずかに)使用し、それらをメインの論理フローに戻します。必要なエラー処理のほとんどは、データ自体の一部として、コード自体から発生する必要があります。返されるデータが特別ではないのに、なぜ特別な条件を作るのですか?

  • 高レベルで1つの大きなハンドラーを使用して、低レベルで捕捉されないコードで発生する奇妙な条件の一部またはすべてを管理します。エラー(ログ、再起動、回復など)で何か役に立つことをしてください。

これら2つのタイプのエラー処理以外に、中央の残りのコードはすべて、try / catchコードとエラーオブジェクトを含まないようにする必要があります。そうすれば、どこで使用しても、何を使っても、期待どおりに簡単に機能します。

ポール。

私は答えに少し遅れているかもしれませんが、エラー処理はいつでもいつでも変更および進化できるものです。このテーマについてもっと詳しく知りたい場合は、新しいブログに投稿しました。 http://taoofdevelopment.wordpress.com

ハッピーコーディング。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top