web-dev-qa-db-ja.com

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

私はアプリケーションで例外を処理する方法を決定するのにこだわっています。

例外に関する私の問題が、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/catchesを介してコードの腐敗に注意するか、例外を尊重しないようにします(例外はシステムに警告しますが、他に警告が必要なものはありますか?)。

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

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

このコメントから、例外を処理するときに考えるいくつかの質問が表示されます。

  • この例外がスローされるポイントは何ですか?
  • それをどのように扱うのが理にかなっていますか?
  • 呼び出し元は本当に例外を気にしますか、それとも呼び出しが成功したかどうかだけを気にしますか?
  • 呼び出し元に潜在的な例外を優雅に管理することを強制していますか?
  • あなたは言語のアイダムに敬意を表していますか?
    • あなたは本当にブール値のような成功フラグを返す必要がありますか?ブール値(またはint)を返すことは、Java(in Java例外を処理するだけです))よりもCの考え方です。
    • 言語に関連するエラー管理構造に従ってください:)!
117
AtariPete

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

ご質問について:

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

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

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

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

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

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

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

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

25
JoshBerke

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

失敗と例外

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

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に変換する必要があります。

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

Anders Hejlsberg:いいえ、多くの場合、人々は気にしません。これらの例外は処理されません。メッセージループの周りには、最下位レベルの例外ハンドラがあります。そのハンドラーは、何が間違っていたかを示すダイアログを表示して続行します。プログラマーは、最終的にどこでもtryを記述してコードを保護するため、例外が発生した場合は正しくバックアウトしますが、実際には例外の処理には関心がありません。

Throws句は、少なくともJavaで実装されている方法では、必ずしも例外の処理を強制するわけではありませんが、処理しない場合は、どの例外が通過する可能性があるかを正確に確認する必要があります。宣言された例外をキャッチするか、独自のthrows句に入れる必要があります。この要件を回避するために、人々はばかげたことをします。たとえば、すべてのメソッドを「例外をスロー」で飾ります。それは機能を完全に打ち負かすだけで、プログラマーにもっと派手なネバネバしたものを書かせただけです。それは誰にも役に立たない。

編集:会話に関する詳細を追加

9
Gant

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

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

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

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

8
Yuval Adam

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

5
Malfist

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

例.

try
{
   sendMessage();

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

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

3
Holograham

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

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

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

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

2
romski

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

また、は、診断目的で例外を記録するときに、 Exception Filterの使用を検討します。 VBには例外フィルターの言語サポートがあります。Gregmgのブログへのリンクには、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のようなパターンを使用するかどうかは、スタイルの問題です。すべての例外をキャッチしてそれらを飲み込むという問題は、スタイルの問題ではありません。予期しない例外が伝播できるようにしてください!

1
Steve Steiner

使用している言語の標準ライブラリからキューを取得することをお勧めします。 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ブロックに多くのステートメントを含めることを目指します。

他の人が指摘しているように、あなたはキャッチする例外の種類をできるだけ具体的にすべきです。

1
slim

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

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

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

1
wds

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

それでも、合理的に使用すると読みやすさの点で驚異的に機能しますが、次の2つの単純なルールに従う必要があります。

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

  • 高レベルで1つの大きなハンドラーを使用して、低レベルでキャッチされないコードで発生する奇妙な条件の一部またはすべてを管理します。エラー(ログ、再起動、回復など)に対して有用なことを行います。

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

ポール。

0
Paul W Homer

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

0
dj_segfault

私の戦略:

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

関数が何かを返す場合、例外/エラーが発生したときにnullを返します。

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

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

0
Germstorm

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

ハッピーコーディング。

0
GRGodoi

Exceptionをキャッチしてfalseを返す場合は、非常に具体的な例外にする必要があります。あなたはそれをしていない、あなたはそれらのすべてをキャッチし、falseを返しています。 MyCarIsOnFireExceptionが発生した場合は、すぐにそれを知りたいです!残りの例外については気にしないかもしれません。そのため、いくつかの例外(「発生したことを説明する新しい例外を再スロー、またはキャッチして再スロー)」に対して「おっと、ここで何かおかしい」と言う例外ハンドラーのスタックが必要です。

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

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

0
jcollum