c# - 汎用 - エラー 処理 設計




JavaまたはC#での例外管理のベストプラクティス (10)

Exceptionをキャッチしてfalseを返す場合は、非常に特殊な例外でなければなりません。 あなたはそれをしていない、あなたはそれらのすべてをキャッチして、falseを返す。 私がMyCarIsOnFireExceptionを取得した場合、すぐにそれについて知りたいです! 私が気にしないかもしれない例外の残りの部分。 だから、いくつかの例外(rethrow、または起こったことをよりよく説明する新しい例外をキャッチして再起)で、 "他の人にはfalseを返す"ために、 "Whoa whoa何かが間違っている"というExceptionハンドラをスタックする必要があります。

これがあなたが立ち上げようとしている製品であれば、それらの例外をどこかでログに記録する必要があります。

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

私はアプリケーションで例外を処理する方法を決定していません。

例外に関する私の問題は、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からのかなりの選択コメントm3rLinEz 。

私はAnders Hejlsbergとあなたに同意しがちです。ほとんどの発信者は、操作が成功するかどうかしか気にしません。

このコメントから、例外を扱うときに考えるべきいくつかの疑問が浮かび上がってきます。

  • この例外がスローされる点は何ですか?
  • どのように扱うのが理にかなっていますか?
  • 呼び出し元は実際に例外を気にしていますか、呼び出しが成功した場合には気にしますか?
  • 潜在的な例外を優雅に管理するように呼び出し元を強制していますか?
  • あなたは言語の義務を尊重していますか?
    • booleanのような成功フラグを本当に返す必要がありますか? 返すブール値(またはint)は、Java(Javaでは例外を処理する)よりもCの考え方です。
    • 言語に関連したエラー管理構造に従ってください:)!

try / catchブロックは、最初の(メイン)セットに埋め込まれたロジックの第2のセットを形成するので、スパゲッティコードをデバッグするのは難しいです。

しかし、合理的に、彼らは読みやすさに驚異を働かせていましたが、ちょうど2つの簡単なルールに従ってください:

  • それらを低レベルで(控えめに)使用して、ライブラリ処理の問題を捕捉し、それらを主な論理フローに戻すことができます。 私たちが望むエラー処理のほとんどは、データそのものの一部としてコード自体から来ているはずです。 なぜ戻ってくるデータが特別でないなら、特別な条件を作るのですか?

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

これらの2つのタイプのエラー処理以外にも、残りのコードはすべて空きで、try / catchコードとエラーオブジェクトから解放されている必要があります。 そうすれば、どこで使っても、それを使って何をするにしても、期待どおりに簡単に動作します。

ポール。


あなたが例外を捕らえてエラーコードに変えたいと思うのは私にとって奇妙なことです。 後者がJavaとC#の両方でデフォルトになっているときに、呼び出し側が例外よりもエラーコードを優先させるのはなぜでしょうか?

あなたの質問については:

  1. 実際に処理できる例外を検出する必要があります。 ただ例外をキャッチすることは、ほとんどの場合に適切なことではありません。 いくつかの例外があります(スレッド間の例外のマーキングやマーシャリングなど)がありますが、そのような場合でも一般的に例外を再現すべきです。
  2. 間違いなくあなたのコードにtry / catchステートメントがたくさんあるはずです。 繰り返しますが、考えられるのは、処理できる例外を捕捉することだけです。 未処理の例外をエンドユーザーにとってはいくらか役に立つものにするために最上位の例外ハンドラを含めることができますが、それ以外の場合はすべての例外をすべての場所でキャッチしようとすべきではありません。

あなたの例でコードパターンを使用する場合は、TryDoSomethingと呼んで、特定の例外だけを捕捉してください。

また、診断の目的で例外をログに記録するときに例外フィルタを使用すること検討してください 。 VBは例外フィルタの言語サポートを持っています。 Greggmのブログへのリンクには、C#から使用できる実装があります。 例外フィルタはcatchとrethrowよりもデバッグ性が優れています。 具体的には、問題をフィルタに記録し、例外が引き続き伝播されるようにすることができます。 このメソッドを使用すると、JIT(Just in Time)デバッガをフル・スタックにすることができます。 Rethrowは、スタックが再投入された時点でスタックを切断します。

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のようなパターンを使用するかどうかは、スタイルに関する疑問です。 すべての例外を捕まえてそれらを飲み込むという問題は、スタイルの問題ではありません。 予期しない例外が伝播されることを確認してください!


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

あなたが遭遇している例外が呼び出し元に意味をなさない(つまり、ネットワーク例外です)場合があります。その場合は、ドメイン固有の例外でそれをラップする必要があります。

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


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

それは例外を飲み込むのは大丈夫ですか? それはあなたのシステムによって異なります。 あなたのアプリケーションが失敗のケースを処理でき、それが失敗した理由をユーザーに通知することによるメリットがない場合は、先に進んでください。ただし、ログに失敗することを強く推奨します。 私はいつも問題のトラブルシューティングを助けるために呼び出されて不満を感じ、例外を飲み込んでいる(または内部の例外を設定せずに代わりに新しいものを投げる)ことを発見しました。

一般的に私は次のルールを使用します:

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

私は次のコードが匂いであることがわかります:

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

このようなコードは意味がなく、含まれてはいけません。


上記のすべてが妥当と思われ、しばしばあなたの職場に方針があるかもしれません。 ここでは、ExceptionのタイプとしてSystemException (未チェック)とApplicationException (チェック済み)を定義しています。

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

ApplicationExceptionは、 InsufficientFundsExceptionようInsufficientFundsExceptionビジネス上の意味を持ち、クライアントコードによって処理される必要があります。

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


例外は、通常のプログラム実行の一部ではないエラーです。 あなたのプログラムが何をしているのか、その使い方(つまりワードプロセッサと心臓モニタ)によって、例外に遭遇したときに違うことをしたいと思うでしょう。 私は、通常の実行の一部として例外を使用するコードで作業しましたが、それは間違いなくコードの匂いです。

Ex。

try
{
   sendMessage();

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

このコードは私をbarfにします。 IMOは、重要なプログラムでなければ例外から回復すべきではありません。 あなたの投げている例外の場合、悪いことが起こっている。


私の戦略:

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

関数が何かを返すべきであれば、例外/エラーが発生したときにnullを返し、そうでなければ返すことのできる項目を返します

boolの代わりに、エラーの説明を含む文字列を返すことができます。

いずれの場合も、何かを返す前にエラーを記録します。


私はその話題に関する別の良い出典をお勧めしたいと思います。 これは、C#とJava、Anders HejlsbergとJames Goslingの発明家とのインタビューで、JavaのChecked Exceptionの話題です。

失敗と例外

また、ページ下部に大きなリソースがあります。

私はAnders Hejlsbergとあなたに同意しがちです。ほとんどの発信者は、操作が成功するかどうかしか気にしません。

Bill Venners :チェックされた例外に関してスケーラビリティとバージョン管理に関する懸念について述べました。 これらの2つの問題が何を意味するのかを明確にすることができますか?

Anders Hejlsberg :バージョニングを開始しましょう。なぜなら、問題はかなり分かりやすいからです。 例外A、B、Cをスローするメソッドfooを作成したとしましょう。fooのバージョン2では、たくさんのフィーチャを追加したいと考えていますが、fooは例外Dをスローする可能性があります。そのメソッドの既存の呼び出し元がその例外をほぼ確実に処理しないため、そのメソッドのthrows節にDを追加します。

新しいバージョンのthrows節に新しい例外を追加すると、クライアントコードが中断されます。 これは、インターフェースにメソッドを追加するようなものです。 インターフェイスをパブリッシュした後は、すべての実用的な目的のために不変です。その実装には、次のバージョンで追加するメソッドが含まれている可能性があります。 だから、代わりに新しいインターフェースを作る必要があります。 例外と同様に、より多くの例外をスローするfoo2という全く新しいメソッドを作成するか、新しいfooで例外Dをキャッチし、DをA、B、またはCに変換する必要があります。

ビル・ヴェネツァーズ :しかし、たとえチェックされた例外がない言語であっても、そのような場合にコードを破ることはありませんか? 新しいバージョンのfooが、クライアントが処理することを考えるべき新しい例外をスローする場合、コードを記述したときにその例外を期待していないという事実だけでコードが壊れていませんか?

Anders Hejlsberg :いいえ、多くの場合、人々は気にしないからです。 彼らはこれらの例外を処理するつもりはない。 メッセージループの周りにボトムレベルの例外ハンドラがあります。 そのハンドラは、何がうまくいかなかったのかを示すダイアログを表示し続けるだけです。 プログラマは、最終的にtryを書いてコードを保護します。したがって、例外が発生した場合には正しく戻ってきますが、実際に例外を処理することには興味はありません。

少なくともJavaで実装されているようなthrows節は、必ずしも例外を処理する必要はありませんが、処理しないと、例外が通過する可能性があることを正確に認識させる必要があります。 宣言された例外をキャッチするか、自分自身のthrows節に入れなければなりません。 この要件を回避するために、人々はばかげたことをする。 たとえば、すべてのメソッドを "throws Exception"で修飾します。 これで機能が完全に無効になり、プログラマーにもっとゴブリンを吐き出させるようにしただけです。 それは誰にも役立たない。

編集:converstaionの詳細を追加





error-handling