98

私の質問は、ほとんどの開発者がエラー処理、例外、またはエラー リターン コードのどちらを好むかということです。特定の言語 (または言語ファミリー) と、どちらかを優先する理由を記入してください。

私は好奇心からこれを尋ねています。個人的には、エラー リターン コードの方が爆発的でなく、ユーザー コードが望まない場合に例外のパフォーマンス ペナルティを支払うことを強制しないため、エラー リターン コードを好みます。

更新:すべての回答に感謝します! 例外を伴うコードフローの予測不可能性は嫌いですが、そう言わざるを得ません。リターンコード(およびその兄のハンドル)に関する答えは、コードに多くのノイズを追加します。

4

24 に答える 24

115

一部の言語 (C++ など) では、リソース リークは理由になりません。

C++ は RAII に基づいています。

失敗、リターン、またはスローする可能性のあるコード (つまり、ほとんどの通常のコード) がある場合は、ポインターをスマート ポインター内にラップする必要があります (スタック上にオブジェクトを作成しない十分な理由があると仮定します)。

戻りコードはより詳細です

それらは冗長であり、次のようなものになる傾向があります。

if(doSomething())
{
   if(doSomethingElse())
   {
      if(doSomethingElseAgain())
      {
          // etc.
      }
      else
      {
         // react to failure of doSomethingElseAgain
      }
   }
   else
   {
      // react to failure of doSomethingElse
   }
}
else
{
   // react to failure of doSomething
}

結局のところ、コードは識別された命令の集まりです (この種のコードは製品コードで見ました)。

このコードは次のように変換できます。

try
{
   doSomething() ;
   doSomethingElse() ;
   doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
   // react to failure of doSomething
}
catch(const SomethingElseException & e)
{
   // react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
   // react to failure of doSomethingElseAgain
}

これは、コードとエラー処理を明確に分離するものであり、これは良いことです。

戻りコードはより脆い

1 つのコンパイラからのあいまいな警告 (「phjr」のコメントを参照) ではない場合、それらは簡単に無視できます。

上記の例で、誰かが考えられるエラーを処理するのを忘れたと仮定します (これは起こります...)。「返された」場合、エラーは無視され、後で爆発する可能性があります (つまり、NULL ポインター)。例外なく同じ問題は発生しません。

エラーは無視されません。爆発させたくない場合もありますが…慎重に選ばなければなりません。

リターン コードを変換する必要がある場合がある

次の関数があるとします。

  • NOT_FOUND_ERROR と呼ばれる int を返すことができる doSomething
  • bool "false" (失敗した場合) を返すことができる doSomethingElse
  • doSomethingElseAgain は、Error オブジェクトを返すことができます (__LINE__、__FILE__、およびスタック変数の半分の両方を使用)。
  • doTryToDoSomethingWithAllThisMess これは、まあ...上記の関数を使用して、タイプのエラーコードを返します...

呼び出された関数の 1 つが失敗した場合の doTryToDoSomethingWithAllThisMess の戻り値の型は何ですか?

リターン コードは普遍的なソリューションではありません

オペレーターはエラー コードを返すことができません。C++ コンストラクターもできません。

リターンコードは、式を連鎖できないことを意味します

上記のポイントの当然の結果。私が書きたいとしたらどうしますか:

CMyType o = add(a, multiply(b, c)) ;

戻り値が既に使用されているため、できません (変更できない場合もあります)。したがって、戻り値が最初のパラメーターになり、参照として送信されます...またはそうではありません。

例外が入力されます

例外の種類ごとに異なるクラスを送信できます。リソース例外 (つまり、メモリ不足) は軽くする必要がありますが、それ以外は必要に応じて重くすることができます (スタック全体を提供する Java 例外が好きです)。

その後、各キャッチを特殊化できます。

再スローせずに catch(...) を使用しないでください

通常、エラーは非表示にしないでください。再スローしない場合は、少なくとも、エラーをファイルに記録するか、メッセージボックスを開くか、何でも...

例外は... NUKE

例外の問題は、それらを使いすぎると、try/catch でいっぱいのコードが生成されることです。しかし、問題は別の場所にあります。誰が STL コンテナーを使用して自分のコードを試したりキャッチしたりするのでしょうか? それでも、これらのコンテナーは例外を送信できます。

もちろん、C++ では、例外をデストラクタから出さないでください。

例外は... 同期

スレッドがひざまずいたり、Windows メッセージ ループ内で伝播したりする前に、必ずそれらをキャッチしてください。

解決策はそれらを混合することでしょうか?

だから、解決策は、何かが起こってはならないときにスローすることだと思います。何かが発生する可能性がある場合は、戻りコードまたはパラメーターを使用して、ユーザーがそれに反応できるようにします。

ですから、唯一の質問は、「起こってはならないことは何ですか?」ということです。

それはあなたの関数の契約に依存します。関数がポインターを受け入れるが、ポインターが非 NULL でなければならないことを指定している場合、ユーザーが NULL ポインターを送信したときに例外をスローしても問題ありません (問題は、C++ では、関数の作成者が代わりに参照を使用しなかった場合です)。ポインターの、しかし...)

別の解決策は、エラーを表示することです

エラーが発生したくないことが問題になる場合があります。例外またはエラー リターン コードを使用するのはクールですが、それについて知りたいと考えています。

私の仕事では、一種の「アサート」を使用します。デバッグ/リリース コンパイル オプションに関係なく、構成ファイルの値に応じて、次のようになります。

  • エラーを記録する
  • 「ねえ、問題があります」というメッセージボックスを開きます
  • 「ねえ、問題があります。デバッグしますか」というメッセージボックスを開きます

開発とテストの両方で、これにより、ユーザーは問題が検出されたときに正確に特定することができます。

従来のコードに簡単に追加できます。例えば:

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}

次のような種類のコードを導きます。

void doSomething(CMyObject * p, int iRandomData)
{
   if(iRandomData < 32)
   {
      MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
      return ;
   }

   if(p == NULL)
   {
      MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
      throw std::some_exception() ;
   }

   if(! p.is Ok())
   {
      MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
   }

   // etc.
}

(デバッグ時にのみアクティブな同様のマクロがあります)。

本番環境では構成ファイルが存在しないため、クライアントはこのマクロの結果を見ることはありません...しかし、必要なときに簡単にアクティブ化できます。

結論

リターン コードを使用してコードを作成するときは、失敗に備えることになり、テストの要塞が十分に安全であることを願っています。

例外を使用してコーディングする場合、コードが失敗する可能性があることを知っており、通常はコード内の選択された戦略的な位置にカウンターファイア キャッチを配置します。しかし通常、あなたのコードは「何が起こるか」よりも「何をしなければならないか」について書かれています。

しかし、コードを作成するときは、自由に使える最適なツールを使用する必要があり、「エラーを隠すのではなく、できるだけ早く表示する」ことが必要な場合もあります。上で話したマクロは、この哲学に従っています。

于 2008-09-21T15:20:36.230 に答える
40

私は実際に両方を使用しています。

既知のエラーの可能性がある場合は、リターン コードを使用します。それが起こる可能性があり、起こると私が知っているシナリオである場合、送り返されるコードがあります.

例外は、私が予期していないことに対してのみ使用されます。

于 2008-09-19T04:46:48.200 に答える
24

Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Librariesの「Exceptions」というタイトルの第 7 章によると、C# などの OO フレームワークで戻り値に対して例外を使用する必要がある理由について、多数の根拠が示されています。

おそらくこれが最も説得力のある理由です (179 ページ)。

「例外はオブジェクト指向言語とうまく統合されます。オブジェクト指向言語は、非オブジェクト指向言語の関数では課されないメンバー シグネチャに制約を課す傾向があります。たとえば、コンストラクタ、演算子のオーバーロード、およびプロパティの場合、開発者は戻り値には選択肢がありません.このため, オブジェクト指向フレームワークの戻り値ベースのエラー報告を標準化することはできません.メソッドシグネチャの帯域外である例外などのエラー報告メソッドが唯一の選択肢です。

于 2008-09-19T08:06:16.130 に答える
11

私の好み(C ++およびPython)は、例外を使用することです。言語が提供する機能により、例外を発生させ、キャッチし、(必要に応じて)再スローするための明確なプロセスが可能になり、モデルが見やすく、使いやすくなります。概念的には、特定の例外を名前で定義し、それに付随する追加情報を含めることができるという点で、戻りコードよりもクリーンです。戻りコードを使用すると、エラー値のみに制限されます(ReturnStatusオブジェクトなどを定義する場合を除く)。

作成しているコードがタイムクリティカルでない限り、スタックの巻き戻しに関連するオーバーヘッドは、心配するほど重要ではありません。

于 2008-09-19T04:50:53.057 に答える
9

例外は、予期しないことが発生した場合にのみ返される必要があります。

例外のもう1つのポイントは、歴史的に、戻りコードは本質的に独自のものであり、成功を示すためにC関数から0が返される場合もあれば、-1が返される場合もあれば、失敗の場合はいずれかが成功の場合は1が返される場合もあります。それらが列挙されている場合でも、列挙はあいまいになる可能性があります。

例外は、より多くの情報を提供することもでき、具体的には「何かがうまくいかなかった、ここに何がありますか、スタックトレースとコンテキストのサポート情報」を詳しく説明します。

そうは言っても、適切に列挙されたリターンコードは、既知の結果のセットに役立つ可能性があります。単純な「関数のn個の結果がここにあり、このように実行されただけです」

于 2008-09-19T04:51:21.713 に答える
7

Javaでは、(次の順序で)使用します:

  1. 契約による設計 (失敗する可能性のあるものを試す前に、前提条件が満たされていることを確認します)。これはほとんどのものをキャッチし、これに対してエラー コードを返します。

  2. 作業の処理中にエラー コードを返す (必要に応じてロールバックを実行する)。

  3. 例外ですが、これらは予期しない場合にのみ使用されます。

于 2008-09-19T04:58:00.420 に答える
6

コード全体で次のパターンが急増するため、リターン コードは嫌いです

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}

すぐに、4 つの関数呼び出しで構成されるメソッド呼び出しが 12 行のエラー処理で肥大化します.そのうちのいくつかは決して起こらないでしょう. if と switch のケースはたくさんあります。

例外を適切に使用すると、例外はよりクリーンになります...例外イベントを通知するために..その後、実行パスを続行できません。多くの場合、エラー コードよりも説明的で情報を提供します。

異なる方法で処理する必要があるメソッド呼び出しの後に複数の状態がある場合 (例外的なケースではない場合)、エラー コードまたは出力パラメーターを使用します。個人的にはこれは珍しいと思いますが..

「パフォーマンスのペナルティ」の反論について少し調べてみました.C++ / COMの世界ではもっと多くのことがわかりましたが、新しい言語では、違いはそれほど大きくないと思います. いずれにせよ、何かが爆発すると、パフォーマンスの問題はバックバーナーに追いやられます:)

于 2008-09-19T05:05:20.247 に答える
5

これについては、少し前にブログ記事を書きました。

例外をスローすることによるパフォーマンスのオーバーヘッドは、決定に影響を与えるべきではありません。ちゃんとやっていれば、やはり例外は例外的です。

于 2008-09-19T05:00:50.740 に答える
4

The Pragmatic Programmerから得た素晴らしいアドバイスは、「プログラムは例外をまったく使用せずにすべての主要機能を実行できるはずです」というものでした。

于 2008-09-19T04:53:47.060 に答える
4

私は単純なルールのセットを持っています:

1) 直接の発信者が反応すると予想されるものにはリターン コードを使用します。

2) 範囲がより広く、エラーの認識が多くの層に浸透してコードをより複雑にする必要がないように、呼び出し元よりもはるかに上のレベルの何かによって処理されることが合理的に期待される可能性のあるエラーには例外を使用します。

Java では、チェックされていない例外しか使用していませんでした。チェックされた例外は、別の形式の戻りコードにすぎません。私の経験では、メソッド呼び出しによって「返される」可能性のあるものの二重性は、一般的に、助けというよりも障害でした。

于 2008-09-19T05:02:53.560 に答える
3

私は、例外的な状況とそうでない状況の両方で、python で例外を使用します。

エラー値を返すのではなく、例外を使用して「リクエストを実行できませんでした」ことを示すことができると便利なことがよくあります。これは、任意に None や NotFoundSingleton などではなく、戻り値が正しい型であることを/常に/知っていることを意味します。これは、戻り値の条件ではなく、例外ハンドラーを使用することを好む良い例です。

try:
    dataobj = datastore.fetch(obj_id)
except LookupError:
    # could not find object, create it.
    dataobj = datastore.create(....)

副作用として、datastore.fetch(obj_id) を実行すると、戻り値が None かどうかを確認する必要がなくなり、無料ですぐにエラーが発生します。これは、「プログラムは、例外をまったく使用せずにすべての主要な機能を実行できる必要がある」という議論に反しています。

競合状態の影響を受けないファイルシステムを処理するためのコードを記述するために、例外が「例外的に」役立つ別の例を次に示します。

# wrong way:
if os.path.exists(directory_to_remove):
    # race condition is here.
    os.path.rmdir(directory_to_remove)

# right way:
try: 
    os.path.rmdir(directory_to_remove)
except OSError:
    # directory didn't exist, good.
    pass

2 つではなく 1 つのシステム コール、競合状態なし。これは、ディレクトリが存在しない場合よりも多くの状況で OSError で失敗することが明らかであるため、悪い例ですが、厳密に制御された多くの状況では「十分な」解決策です。

于 2008-09-19T05:12:00.107 に答える
3

適切なコンパイラまたはランタイム環境の例外があれば、重大なペナルティは発生しません。これは、例外ハンドラにジャンプする GOTO ステートメントに多かれ少なかれ似ています。また、例外をランタイム環境 (JVM など) でキャッチすると、バグの分離と修正がはるかに簡単になります。私はいつでも C の segfault を越えて Java の NullPointerException を取るつもりです。

于 2008-09-19T04:47:07.637 に答える
3

リターン コードがコード ノイズを増やすと思います。たとえば、リターン コードが原因で、COM/ATL コードの外観が常に嫌いでした。コードのすべての行に対して HRESULT チェックが必要でした。エラー リターン コードは、COM のアーキテクトが行った不適切な決定の 1 つだと思います。コードを論理的にグループ化することが困難になるため、コードのレビューが難しくなります。

行ごとに戻りコードの明示的なチェックがある場合のパフォーマンスの比較についてはわかりません。

于 2008-09-19T05:29:31.937 に答える
3

例外はエラー処理、IMO ではありません。例外はそれだけです。あなたが予期していなかった例外的な出来事。私が言う注意して使用してください。

エラーコードは問題ありませんが、メソッドから 404 または 200 を返すのは良くありません。代わりに列挙型 (.Net) を使用すると、コードが読みやすくなり、他の開発者が使いやすくなります。また、数字と説明の表を維持する必要もありません。

また; 私の本では、try-catch-finally パターンはアンチパターンです。try-finally は良いもので、try-catch も良いものですが、try-catch-finally は決して良いものではありません。try-finally は、多くの場合、「using」ステートメント (IDispose パターン) に置き換えることができます。これは、IMO の方が優れています。そして、処理できる例外を実際にキャッチする Try-catch が良いです。または、これを行う場合:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

したがって、例外がバブルし続ける限り、問題ありません。別の例は次のとおりです。

try{
    dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
    logger.Exception(ex, "Connection failed");
    dbHasBeenUpdated = false;
}

ここで実際に例外を処理します。update メソッドが失敗したときに try-catch の外で何をするかは別の話ですが、私の主張は成されたと思います。:)

では、なぜ try-catch-finally がアンチパターンなのですか? 理由は次のとおりです。

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}
finally {
    db.Close();
}

db オブジェクトが既に閉じられている場合はどうなりますか? 新しい例外がスローされ、処理する必要があります! これの方が良い:

try{
    using(IDatabase db = DatabaseFactory.CreateDatabase()) {
        db.UpdateAll(somevalue);
    }
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

または、db オブジェクトが IDisposable を実装していない場合は、次のようにします。

try{
    try {
        IDatabase db = DatabaseFactory.CreateDatabase();
        db.UpdateAll(somevalue);
    }
    finally{
        db.Close();
    }
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
    logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

とにかくそれは私の2セントです!:)

于 2008-09-19T07:41:22.337 に答える
2

関数の通常の結果として、エラー処理と戻り値(またはパラメーター)に例外を使用することを好みます。これにより、簡単で一貫性のあるエラー処理スキームが提供され、正しく実行されれば、見た目がはるかにクリーンなコードになります。

于 2008-09-19T04:55:30.770 に答える
2

大きな違いの1つは、例外によってエラーの処理が強制されるのに対し、エラーリターンコードはチェックされないままになる可能性があることです。

エラーリターンコードを頻繁に使用すると、次の形式のifテストが多数含まれる非常に醜いコードが発生する可能性があります。

if(function(call) != ERROR_CODE) {
    do_right_thing();
}
else {
    handle_error();
}

個人的には、呼び出し元のコードが対処する必要がある、または対処しなければならないエラーには例外を使用し、何かを返すことが実際に有効で可能である「予期される失敗」にはエラーコードのみを使用することを好みます。

于 2008-09-19T04:56:54.260 に答える
2

戻りコードよりも例外を好む理由はたくさんあります。

  • 通常、読みやすくするために、メソッド内の return ステートメントの数を最小限に抑えようとします。そうすることで、例外は、不完全な状態にある間に余分な作業を行うことを防ぎ、それにより、より多くのデータを損傷する可能性を防ぎます。
  • 例外は一般に、戻り値よりも冗長であり、拡張が容易です。メソッドが自然数を返し、エラーが発生したときに戻りコードとして負の数を使用すると仮定すると、メソッドのスコープが変更されて整数を返すようになった場合、少し微調整するのではなく、すべてのメソッド呼び出しを変更する必要があります。例外。
  • 例外を使用すると、通常の動作のエラー処理をより簡単に分離できます。一部の操作がアトミック操作として何らかの形で実行されるようにすることができます。
于 2008-09-19T05:01:50.490 に答える
1

私は例外のみを使用し、リターンコードは使用しません。ここではJavaについて話しています。

私が従う一般的なルールは、呼び出されたメソッドがdoFoo()ある場合、それが「fooを実行」しない場合、何か例外が発生したため、例外がスローされるということです。

于 2008-09-19T04:52:09.670 に答える
1

例外について私が恐れていることの1つは、例外をスローするとコードフローが台無しになることです。たとえば、あなたがする場合

void foo()
{
  MyPointer* p = NULL;
  try{
    p = new PointedStuff();
    //I'm a module user and  I'm doing stuff that might throw or not

  }
  catch(...)
  {
    //should I delete the pointer?
  }
}

またはさらに悪いことに、私が持ってはいけないものを削除したが、残りのクリーンアップを行う前にキャッチするために投げられた場合はどうなりますか。投げることは貧しいユーザーの私見に大きな重みを置きました。

于 2008-09-19T04:55:19.740 に答える
1

例外と戻りコードの引数における私の一般的なルール:

  • ローカリゼーション/国際化が必要な場合はエラーコードを使用します。.NET では、これらのエラーコードを使用してリソース ファイルを参照し、適切な言語でエラーを表示できます。それ以外の場合は、例外を使用してください
  • 本当に例外的なエラーに対してのみ例外を使用してください。かなり頻繁に発生する場合は、ブール値または列挙型のエラーコードを使用してください。
于 2008-09-19T04:58:25.817 に答える
1

戻りコードが例外ほど醜くないとは思いません。例外を除いて、try{} catch() {} finally {}戻りコードと同じように where がありますif(){}。投稿に記載されている理由から、私は例外を恐れていました。ポインターをクリアする必要があるかどうかわかりません。しかし、リターンコードに関しては同じ問題があると思います。問題の関数/メソッドに関する詳細を知らない限り、パラメーターの状態はわかりません。

とにかく、可能であればエラーを処理する必要があります。戻りコードを無視してプログラムに segfault をさせるのと同じくらい簡単に、例外を最上位に伝搬させることができます。

結果に対して値 (列挙型?) を返し、例外的な場合には例外を返すというアイデアが気に入っています。

于 2008-09-19T05:09:09.097 に答える