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


117

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

例外に関する私の問題が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つであるようです。しかし、最も重要なのは、システム内の例外を管理する方法に一貫性が必要です。

さらに、過度の試行/キャッチによるコードの腐敗に注意するか、例外を尊重しないようにしてください(例外はシステムに警告していますが、他に何を警告する必要がありますか?)。

また、これはm3rLinEzからのかなり良いコメントです

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

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

  • この例外がスローされる点は何ですか?
  • それを処理することはどのように意味がありますか?
  • 呼び出し元は本当に例外を気にしますか、それとも呼び出しが成功したかどうかだけ気にしますか?
  • 発信者に潜在的な例外の管理を優雅に強制していますか?
  • 言語の偶像に敬意を払っていますか?
    • 本当にブールのような成功フラグを返す必要がありますか?boolean(またはint)を返すことは、Java(Javaでは例外を処理するだけ)よりもCの考え方に近いものです。
    • 言語に関連付けられているエラー管理構造に従ってください:)!

オラクルコミュニティの記事はJavaで書かれていますが、非常に幅広い言語のクラスに対する一般的なアドバイスです。素敵な記事、ちょうど私が探していたもの。
バニーウサギ

回答:


61

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

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

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

なぜ誰かが例外の代わりにエラーコードを必要とするのかについて...私のアプリケーションが例外を生成しているにもかかわらず、HTTPがエラーコードを使用するのは奇妙だといつも思っていました。HTTPで例外をそのまま渡すことができないのはなぜですか?
トレカズ2012

あなたができる最善のことは、モナドでエラーコードをラップすることです
lindenrovio

@Trejkaz例外の詳細をユーザーに返さないでください。これはセキュリティ上のリスクがあるためです。これが、HTMLサーバーがエラーコードを返す理由です。エラーメッセージを返すのはローカリゼーションの問題でもあり、おそらくHTMLのサイズが大きくなり、返すのが遅くなります。これらすべてが、HTMLサーバーがエラーコードを返すと思う理由です。
Didier A.

「実際に処理できる例外のみを抑制すべきだ」と言った方がいいと思います。
Didier A.

@didibusセキュリティ上の懸念から開発に不便が生じると思われるのはなぜですか?制作については何も言わなかった。エラーメッセージのローカリゼーションについては、Webサイト全体に既にその問題があり、人々は対処しているようです。
Trejkaz 14年

25

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

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

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

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

次のコードはにおいだと思います:

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

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


@ジョシュ、例外を飲み込むのは良い点ですが、単に例外を飲み込むことが許容されるケースはほとんどないと思います。前のプロジェクトでは、コードスニペットが必須でしたが、それは悪臭を放ちました。それらをすべてログに記録すると、最悪の事態になりました。例外を処理できない場合は、飲み込まないようにしてください。
smaclell

私が言ったように、これはすべてアプリと特定のコンテキストに依存することに同意します。まれではありますが、例外を飲み込んだり、最後に行った時間を思い出せなかったりすることがあります。自分のロガーを書き込んでいて、ログへの書き込みを行っていたときに、セカンダリログが失敗した可能性があります。
JoshBerke 2009年

コードはポイントを提供します。「スロー」にブレークポイントを設定できます。
ラウホッツ2009年

3
弱点は、スローされた例外で中断するようにVSに指示するか、それを絞り込んで特定の例外を選択することです。VS2008には、デバッグ中のメニュー項目があります(ツールバーをカスタマイズするには、ツールバーをカスタマイズする必要があります)Called Exceptions
JoshBerke

「コードのにおい」の例には、その単純な形式でも明確な副作用があります。スローするポイントの周りにブロックが// do something含まれている場合try/finallyfinallyブロックはcatchブロックの前に実行されます。がなければ、try/catch例外はfinallyブロックが実行されずにスタックの一番上まで飛ぶでしょう。これにより、トップレベルのハンドラーがfinallyブロックを実行するかどうかを決定できます。
Daniel Earwicker、2010年

9

このトピックについて別の優れた情報源をお勧めします。これは、Javaのチェック例外についての、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 finallyをどこにでも書いてコードを保護するので、例外が発生しても正しくバックアウトしますが、実際には例外の処理に関心がありません。

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

編集:会話の詳細を追加しました


これをありがとう!私はあなたの答えについての情報で私の質問を更新しました!
AtariPete 2009年

Hejlsberg氏がポケモンの例外処理を正当化しているようです。JavaおよびC#での例外の設計に関する大きな問題の1つは、例外のタイプにエンコードされている情報が多すぎる一方で、情報を例外インスタンスに保存する必要があるため、一貫性のある方法で利用できないことです。例外は、それによって表されるすべての異常な状態が解決されるまで、コールスタックまで伝播する必要があります。残念ながら、例外のタイプは、たとえ認識されたとしても、状況が解決されたかどうかを示すにはほとんど役立ちません。例外が認識されない場合
スーパーキャット2013年

...状況はさらに悪化しています。コードが呼び出されFetchData、それが予期しないタイプの例外をスローする場合、その例外が単にデータが利用できないことを意味するかどうかを知る方法はありません(この場合、コードなしで取得できる機能は、データを「解決」します)。それがCPUに火がついていることを意味し、システムが最初の機会に「安全シャットダウン」を行うべきかどうか。Hejlsberg氏はコードで前者を想定するよう提案しているようです。おそらく、既存の例外階層を考えると、それが最善の戦略である可能性がありますが、それでも不気味なようです。
スーパーキャット2013年

Exceptinのスローはばかげていることに同意します。ほとんどすべてのものが例外をスローする可能性があるため、これが発生する可能性があると想定する必要があります。ただし、スローA、B、Cなどの例外を指定する場合、それは単なる注釈であり、私にとっては、コメントブロックのように使用する必要があります。それは、私の機能のクライアントです。ここにヒントがあります。A、B、Cに対処したいのですが、私を使用するときにこれらのエラーが発生する可能性が高いからです。あなたが将来的にDを追加した場合、それが処理されていない場合は大したことではないのですが、それは、今、またD.をキャッチするために有用であろう方法によってああ、新しいドキュメントを追加するようなものだ
ディディエ・A.

8

チェックされた例外は、一般的に、特にJavaで、論争の的になっている問題です(後で私は、賛成で反対の例をいくつか見つけようとします)。

経験則として、例外処理はこれらのガイドラインに沿ったものであり、特定の順序ではありません。

  • 保守性のために、常に例外をログに記録して、バグが発生し始めたときに、ログが、バグが発生しそうな場所を特定できるようにします。決して離れprintStackTrace()たりしないでください。いずれかのユーザーがいずれかのスタックトレースを最終的に取得し、それをどうするかについてまったく知識がない可能性があります。
  • 処理できる例外をキャッチそれらのみを処理してそれらを処理します。それらをスタックに投げるだけではありません。
  • 常に特定の例外クラスをキャッチします。通常はtypeをキャッチしないExceptionでください。重要な例外を飲み込む可能性が非常に高くなります。
  • 決して(決して)Errorsをキャッチしないでください!! 、意味:sは後者のサブクラスであるため、決してThrowablesキャッチしないでくださいErrorErrorsは、処理できない可能性が最も高い問題(例OutOfMemory、または他のJVMの問題)です。

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


チェックされた例外はC#では問題がないため、C#では問題になりません。
cletus 2009年

3
Imho、時々エラーをキャッチするのは良いことです:私は私が書いた非常にメモリを集中的に使用するJavaアプリを覚えています。私はOutOfMemory-Exをキャッチし、メモリが不足していること、他のプログラムを終了し、より多くのヒープスペースを割り当ててjvmを起動する方法をユーザーに伝えるメッセージをユーザーに示しました。助けになったと思います。
Lena Schimmel、

5

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


3

例外は、通常のプログラム実行の一部ではないエラーです。プログラムの動作とその用途(ワードプロセッサとハートモニターなど)によっては、例外が発生したときにさまざまな処理を実行する必要があります。通常の実行の一部として例外を使用するコードを使用してきましたが、それは間違いなくコードのにおいです。

try
{
   sendMessage();

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

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


2

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

SystemExceptionsが回復可能である可能性は低く、上部で一度処理されることに同意しました。さらにコンテキストを提供するために、私たちのSystemExceptionSは、例えば、彼らが発生した場所を示すためにextenededされているRepositoryExceptionServiceEceptionなど

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

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


1

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

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

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


1

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

また、診断目的で例外を記録する場合は例外フィルターの使用を検討してください。VBは、例外フィルターの言語サポートを備えています。Greggmのブログへのリンクには、C#から使用できる実装があります。例外フィルターには、キャッチおよび再スローよりもデバッグ性に優れたプロパティがあります。具体的には、問題をフィルターに記録し、例外の伝播を継続させることができます。この方法では、JIT(ジャストインタイム)デバッガーをアタッチして、元のスタック全体を保持できます。再スローは、再スローされた時点でスタックを切り離します。

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


.netのTryXXXパターンが大好きです。
JoshBerke 2009年

1

使用している言語の標準ライブラリからキューを取得することをお勧めします。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の世界にはいません。

例外を採用すると、エラー処理コードをコアロジックから遠ざけることで、コードがよりシンプルで明確になることがわかります。1つのtryブロックで多くのステートメントを持つことを目指します。

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


これは非常に有効なコメントです。言語を尊重し、伝統的にそのような問題をどのように管理するかを尊重してください。Cの考え方をJavaの世界に持ち込まないでください。
AtariPete 2009年

0

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

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

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


例外ブロックの設定にもコストがかかることを忘れないでください
devstuff

0

私の戦略:

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

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

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

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


0

ここにいくつかの優れた答えがあります。私が付け加えておきたいのは、あなたが投稿したようなものに終わったら、少なくともスタックトレースより多くを出力することです。当時何をしていたのか、そしてEx.getMessage()を言って、開発者に戦いの機会を与えます。


私は完全に同意します。私はEx.printStackTrace();だけを行いました。私がキャッチで行っていた(つまり、再スローしない)例として。
AtariPete 2009年

0

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

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

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

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

これらの2種類のエラー処理を除いて、中間にある残りのコードはすべて、try / catchコードとエラーオブジェクトから解放されている必要があります。このように、どこで使用するか、何を使用するかに関係なく、期待どおりに簡単に機能します。

ポール。


0

私は答えに少し遅れるかもしれませんが、エラー処理は私たちが常に変化し、時間とともに進化することができるものです。この主題についてもっと読みたいと思うなら、私はそれについて私の新しいブログに投稿を書きました。http://taoofdevelopment.wordpress.com

ハッピーコーディング。

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