例外コードとリターンコードのどちらが好きですか。また、その理由は何ですか。


92

私の質問は、ほとんどの開発者がエラー処理、例外、またはエラーリターンコードに対して何を好むかということです。言語(または言語族)固有であり、なぜ一方を他方よりも好むのかを教えてください。

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

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


1
ソフトウェアの世界からの鶏が先か卵が先かという問題...永遠に議論の余地があります。:)
Gishu

申し訳ありませんが、さまざまな意見を持っていることが、人々(私を含む)が適切に選択するのに役立つことを願っています。
ロバートグールド

回答:


107

一部の言語(つまり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
  • doSomethingElse、ブール値「false」を返すことができます(失敗した場合)
  • doSomethingElseAgainは、Errorオブジェクト(__LINE __、__ FILE__、およびスタック変数の半分の両方を含む)を返すことができます。
  • doTryToDoSomethingWithAllThisMess、まあ...上記の関数を使用して、タイプのエラーコードを返します...

呼び出された関数の1つが失敗した場合、doTryToDoSomethingWithAllThisMessが返されるタイプは何ですか?

リターンコードは普遍的な解決策ではありません

演算子はエラーコードを返すことはできません。C ++コンストラクターもできません。

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

上記の点の当然の結果。書きたい場合はどうすればよいですか?

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

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

例外が入力されます

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

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

再スローせずに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.
}

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

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

結論

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

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

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


3
ええ、if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
しかし

なぜですか...しかし、doSomething(); doSomethingElse(); ...if / while / etcを追加する必要がある場合は、まだ良いと思います。通常の実行目的のステートメントでは、if / while / etcと混合したくありません。例外的な目的で追加されたステートメント...そして、例外の使用に関する実際のルールは、キャッチではなくスローであるため、try / catchステートメントは通常侵襲的ではありません。
paercebal 2012年

1
最初のポイントは、例外の問題が何であるかを示しています。制御フローがおかしくなり、実際の問題から切り離されます。それは、いくつかのレベルの識別をキャッチのカスケードに置き換えています。発生する可能性のあるエラーにはリターンコード(または重い情報を含むリターンオブジェクト)を使用し、予期しないものには例外を使用します。
ピーター

2
@Peter Weber:それは奇妙ではありません。通常の実行フローの一部ではないため、実際の問題から分離されています。それは例外的な実行です。そして、繰り返しになりますが、例外についてのポイントは、例外的なエラーの場合、頻繁スローしキャッチすることはほとんどありません。したがって、catchブロックがコードに表示されることはめったにありません。
paercebal 2013年

この種の議論の例は非常に単純です。通常、「doSomething()」または「doSomethingElse()」は、オブジェクトの状態を変更するなど、実際に何かを実行します。例外コードは、オブジェクトが前の状態に戻ることを保証するものではなく、キャッチがスローから非常に遠い場合はさらに少なくなります...例として、doSomethingが2回呼び出され、スローする前にカウンターをインクリメントするとします。例外をキャッチするときに、1回または2回デクリメントする必要があることをどのように知っていますか?一般に、おもちゃの例ではないものに対して例外安全コードを書くことは非常に困難です(不可能ですか?)。
xryl669 2016

36

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

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

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


非常に簡単な方法で+1します。少なくとも私がそれを非常に速く読むことができるように十分に短い。:-)
Ashish Gupta

1
例外は、私が予期していないことにのみ使用されます例外が予期されていない場合、なぜ、またはどのように使用できるのでしょうか。
anar khalilov 2013

5
それが起こるとは思っていなかったからといって、どうしてそうなるのかわからないというわけではありません。SQLServerがオンになって応答することを期待しています。ただし、予期しないダウンタイムが発生した場合に正常に失敗できるように、期待値をコーディングしています。
スティーブンライトン2013

応答しないSQLServerは、「既知の考えられるエラー」に簡単に分類されませんか?
tkburbidge

21

フレームワーク設計ガイドラインの「例外」というタイトルの第7章によると、再利用可能な.NETライブラリの規則、イディオム、およびパターン、C#などのオブジェクト指向フレームワークで戻り値に対して例外を使用する必要がある理由について多くの理由が示されています。

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

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


10

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

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


2
例外を使用すると、プログラムの分析が難しくなることに注意してください。
パヴェルHajdan

7

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

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

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

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


6

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

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

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

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


1
契約にアサーションを使用する方が少し正しいのではないでしょうか。契約が破られた場合、あなたを救うものは何もありません。
パヴェルHajdan

@PawełHajdan、アサーションはデフォルトで無効になっていると思います。これは、assert常にアサーションを使用して実行しない限り、本番コードの問題をキャッチしないという点で、Cのものと同じ問題があります。私は、開発中にキャッチ問題への道としてアサーションを参照する傾向があるが、唯一の(例えば、定数、とのものなどないアサート一貫主張またはだろうもののためではない変数や、実行時に変更することができない何かを持つもの)。
paxdiablo

そしてあなたの質問に答えるために12年。ヘルプデスクを運営する必要があります:
paxdiablo

6

リターンコードは、ほぼ毎回「PitofSuccess」テストに失敗します。

  • リターンコードを確認するのを忘れて、後で赤ニシンエラーが発生するのは簡単です。
  • リターンコードには、コールスタック、内部例外などの優れたデバッグ情報は含まれていません。
  • リターンコードは伝播せず、上記の点に加えて、一元化された場所(アプリケーションおよびスレッドレベルの例外ハンドラー)にログを記録する代わりに、過剰で織り交ぜられた診断ログを駆動する傾向があります。
  • 戻りコードは、ネストされた「if」ブロックの形式で厄介なコードを駆動する傾向があります
  • 明らかな例外(成功の落とし穴)であったはずの未知の問題のデバッグに開発者が費やした時間は高くつきます。
  • C#の背後にあるチームが制御フローを管理するための例外を意図していなかった場合、例外は入力されず、catchステートメントに「when」フィルターはなく、パラメーターのない「throw」ステートメントは必要ありません。 。

パフォーマンスについて:

  • 例外は、まったくスローしないのに比べて計算コストが高くなる可能性がありますが、理由からEXCEPTIONSと呼ばれます。速度の比較では、常に100%の例外率を想定していますが、これは決して当てはまりません。例外が100倍遅い場合でも、それが1%の時間しか発生しない場合、それは実際にどれほど重要ですか?
  • グラフィックアプリケーションなどの浮動小数点演算について話していない限り、CPUサイクルは開発者の時間に比べて安価です。
  • 時間の観点からのコストも同じ議論をします。データベースクエリ、Webサービス呼び出し、またはファイルの読み込みに比べて、通常のアプリケーション時間は例外時間を小さくします。2006年の例外はほぼマイクロ秒未満でした
    • .netで作業している人なら誰でも、デバッガーを設定してすべての例外を中断し、コードだけを無効にして、知らない例外がいくつ発生しているかを確認することを敢えてします。

4

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


4
あなたはそれを誤解しています。彼らが意味したのは、「プログラムが通常のフローで例外をスローする場合、それは間違っている」ということでした。言い換えれば、「例外的なものにのみ例外を使用する」ということです。
パヴェルHajdan

4

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

例外をスローすることによるパフォーマンスのオーバーヘッドは、決定に影響を与えるべきではありません。結局のところ、正しく実行している場合、例外は例外です。


リンクされたブログ投稿は、飛躍する前の確認(チェック)と、許可(例外またはリターンコード)よりも許しを求めるのが簡単なことに関するものです。私はその問題についての私の考えをそこで返信しました(ヒント:TOCTTOU)。しかし、この質問は別の問題についてです。つまり、特別なプロパティを持つ値を返すのではなく、言語の例外メカニズムを使用する条件についてです。
ダミアン・イェリック2016年

同意します。過去9年間で1つか2つのことを学んだようです;)
Thomas

4

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

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

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

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

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

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


3

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


2
例外は非常に高価です。彼らは潜在的な例外ハンドラーを見つけるためにスタックを歩かなければなりません。このスタックウォークは安くはありません。スタックトレースが作成されると、スタック全体を解析する必要があるため、さらにコストがかかります。
デレクパーク

コンパイラーが、少なくとも一部の時間に例外がキャッチされる場所を判別できないことに驚いています。さらに、例外によってコードのフローが変わるという事実により、エラーが発生した場所を正確に特定しやすくなり、私の意見では、パフォーマンスの低下を補っています。
カイルクロニン

コールスタックは実行時に非常に複雑になる可能性があり、コンパイラは通常、そのような分析を行いません。たとえそうだったとしても、トレースを取得するにはスタックを歩く必要があります。またfinally、スタックに割り当てられたオブジェクトのブロックとデストラクタを処理するには、スタックを巻き戻す必要があります。
デレクパーク

ただし、例外のデバッグの利点がパフォーマンスコストを補うことが多いことに同意します。
デレクパーク

1
デラックパーク、例外が発生した場合は高額です。これが、使いすぎてはいけない理由です。しかし、それらが起こらなければ、実質的に何の費用もかかりません。
paercebal 2008

3

簡単なルールセットがあります。

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

2)範囲が広く、呼び出し元の何レベルも上のレベルで処理されることが合理的に予想されるエラーには例外を使用します。これにより、エラーの認識が多くのレイヤーに浸透する必要がなくなり、コードがより複雑になります。

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


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で失敗することは明らかであるため、悪い例ですが、厳密に制御された多くの状況では「十分」なソリューションです。


2番目の例は誤解を招くものです。os.path.rmdirコードは例外をスローするように設計されているため、おそらく間違った方法が間違っています。リターンコードフローの正しい実装は、 'if rmdir(...)== FAILED:pass'
MaR 2013

3

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

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


1
例外をサポートしない言語で使用できるように設計されたCOMワッド。
ケビン

それは良い点です。スクリプト言語のエラーコードを処理することは理にかなっています。少なくともVB6は、エラーコードの詳細をErrオブジェクトにカプセル化することでうまく隠します。これは、コードをよりクリーンにするのに役立ちます。
rpattabi 2008

私は同意しません:VB6は最後のエラーのみをログに記録します。悪名高い「onerrorresume next」と組み合わせると、問題が発生するまでに、問題の原因を完全に見逃してしまいます。これがWin32APIIでのエラー処理の基礎であることに注意してください(GetLastError関数を参照)
paercebal 2008

2

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


2

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

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

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

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


少なくともC / C ++とgccでは、戻り値が無視されたときに警告を生成する属性を関数に与えることができます。
パヴェルHajdan

phjr:「エラーコードを返す」パターンには同意しませんが、あなたのコメントはおそらく完全な答えになるはずです。十分面白いと思います。少なくとも、それは私に有用な情報を与えてくれました。
paercebal 2008

2

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

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

2

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

エラーコードは問題ありませんが、メソッドから404または200を返すのは悪いです、IMO。代わりに列挙型(.Net)を使用してください。これにより、コードが読みやすくなり、他の開発者にとって使いやすくなります。また、番号と説明の表を維持する必要はありません。

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

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;
}

ここで私は実際に例外を処理します。更新メソッドが失敗したときに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セントです!:)


オブジェクトに.Close()があり、.Dispose()がない場合は奇妙になります
abatishchev 2009

例としてClose()のみを使用しています。それを他のものとして自由に考えてください。私が述べているように; 可能な場合は、使用パターンを使用する必要があります(doh!)。もちろん、これは、クラスがIDisposableを実装していることを意味するため、Disposeを呼び出すことができます。
noocyte 2009

1

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

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


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?
  }
}

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


これが<code> finally </ code>ステートメントの目的です。しかし、残念ながら、C ++標準には含まれていません...–
Thomas

C ++では、「construcotorでリソースを取得し、destrucotorでリソースを解放する」という経験則に従う必要があります。この特定のケースでは、auto_ptrが完全に機能します。–
Serge

トーマス、あなたは間違っています。C ++はそれを必要としないので、最終的にはありません。代わりにRAIIがあります。Sergeのソリューションは、RAIIを使用した1つのソリューションです。
paercebal 2008

ロバート、セルジュのソリューションを使用すれば、問題が解決することがわかります。さて、スローよりも多くのトライ/キャッチを書く場合、(コメントを判断することによって)おそらくコードに問題があります。もちろん、再スローせずにcatch(...)を使用すると、エラーが隠されて無視されやすくなるため、通常は不適切です。
paercebal 2008

1

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

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

l10n / i18nを実行すると、例外を使用できなかったのに理由はありません。例外には、ローカライズされた情報も含まれる場合があります。
ギズモ

1

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

とにかく、可能であればエラーを処理する必要があります。戻りコードを無視してプログラムをセグメンテーション違反させるのと同じくらい簡単に、例外をトップレベルに伝播させることができます。

結果の値(列挙?)と例外的な場合の例外を返すというアイデアが好きです。


0

Javaのような言語の場合、例外が処理されないとコンパイラーがコンパイル時エラーを出すため、Exceptionを使用します。これにより、呼び出し元の関数が例外を処理/スローします。

Pythonの場合、私はもっと対立しています。コンパイラがないため、呼び出し元が関数によってスローされた例外を処理せず、実行時例外が発生する可能性があります。リターンコードを使用する場合、適切に処理されないと予期しない動作が発生する可能性があり、例外を使用すると実行時例外が発生する可能性があります。


0

失敗が例外的であるかどうかを呼び出し元が判断できるため一般的にリターンコードを好みます

このアプローチは、Elixir言語では一般的です。

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

リターンコードを使用すると、ネストされたifステートメントが多数発生する可能性があると言われていますが、より適切な構文で処理できます。Elixirでは、このwithステートメントを使用すると、一連のハッピーパスの戻り値を障害から簡単に分離できます。

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

Elixirには、例外を発生させる機能がまだあります。最初の例に戻ると、ファイルに書き込めない場合は、これらのいずれかを実行して例外を発生させることができます。

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

呼び出し元として、書き込みが失敗した場合にエラーを発生させたいことがわかっている場合は、のFile.write!代わりに呼び出すことを選択できますFile.write。またはFile.write、失敗の考えられる理由をそれぞれ異なる方法で呼び出して処理することを選択できます。

もちろんrescue、必要に応じて例外が発生する可能性は常にあります。しかし、有益な戻り値を処理することと比較すると、それは私には厄介に思えます。関数呼び出しが失敗する可能性がある、または失敗するはずであることがわかっている場合、その失敗は例外的なケースではありません。

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