1. ホーム
  2. language-agnostic

[解決済み] 例外処理とリターンコードのどちらが好きか、またその理由は?

2023-01-07 11:13:37

質問

私の質問は、ほとんどの開発者がエラー処理のために、例外とエラーリターンコードのどちらを好むかということです。言語(または言語ファミリー)を特定し、なぜ他より1つを好むのかを教えてください。

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

update: すべての回答をありがとうございました。私は例外を含むコード フローの予測不可能性が嫌いですが、そう言わざるを得ません。リターン コード (およびその兄貴分であるハンドル) についての回答は、コードに多くのノイズを追加します。

どのように解決するのですか?

いくつかの言語 (例: 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
}

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

リターンコードがよりもろくなる

あるコンパイラからの不明瞭な警告でなければ ("phjr" のコメントを参照)、それらは簡単に無視することができます。

上記の例で、誰かがその可能性のあるエラーを処理するのを忘れたと仮定します (これは起こることです...)。エラーは "returned" のときに無視され、おそらく後で爆発します (つまり、NULL ポインタ)。同じ問題は、例外では起こりません。

エラーは無視されません。爆発しないようにしたい場合もありますが...。なので、慎重に選ぶ必要があります。

リターンコードの翻訳が必要な場合がある

例えば以下のような関数があるとします。

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

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

リターンコードは万能ではありません

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

リターンコードとは、式を連鎖させることができないことを意味する

上の指摘の帰結。書きたい場合はどうするか。

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

返り値がすでに使われているので、できません(変更できないこともあります)。つまり、戻り値が最初のパラメータになり、参照として送られる...。あるいはそうでないか。

例外は型付けされる

例外の種類ごとに異なるクラスを送信することができます。リソース例外(例:メモリ不足)は軽くすべきですが、それ以外は必要なだけ重くできます(私はスタック全体を渡すJava例外が好きです)。

それぞれのキャッチは特化することができます。

再スローせずにcatch(...)を使用することはありません。

通常、エラーを隠すべきではありません。再スローしない場合は、最低限、ファイルにエラーを記録する、メッセージボックスを開く、など...。

例外は... NUKE

例外の問題は、例外を使いすぎるとtry/catchだらけのコードになってしまうことです。しかし、問題は別のところにあります。STLコンテナを使ったコードでtry/catchをする人がいるでしょうか?それでも、これらのコンテナは例外を送信することができます。

もちろん、C++では、デストラクタから例外を出さないようにしましょう。

例外は...同期

例外が発生してスレッドが落ちたり、Windowsのメッセージループの中で伝播したりする前に、必ずキャッチしてください。

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

だから私は解決策は、何かがべきであるときに投げることだと思います。 ではなく を投げることです。そして、何かが起こる可能性があるとき、ユーザーがそれに反応できるようにリターンコードまたはパラメータを使用します。

つまり、唯一の問題は、「起きてはならないこと」とは何か、ということです。

それは、関数の契約に依存します。もし関数がポインタを受け取るが、そのポインタは非NULLでなければならないと指定しているなら、ユーザーがNULLポインタを送ったときに例外を投げてもよいでしょう(問題は、C++では、関数作成者がいつポインタではなく参照を使わなかったか、ですが...)。

もうひとつの解決策は、エラーを表示することです。

時には、エラーを出したくないという問題があります。例外やエラーのリターンコードを使うのはクールですが 知っておきたいものです。

私の仕事では、一種の "Assert" を使用します。それは、設定ファイルの値によって、デバッグ/リリースコンパイルのオプションに関係なく、そうなります。

  • エラーをログに記録する
  • メッセージボックスを開き、"Hey, you have a problem".と表示します。
  • メッセージ ボックスを "問題があります、デバッグしますか。

開発およびテストの両方において、これによりユーザーは、問題が検出された後ではなく、正確に問題を突き止めることができます (戻り値を気にするコードがある場合、または catch の内部で)。

レガシーコードに追加するのは簡単です。例えば

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

(デバッグ時のみ有効な同様のマクロがあります)。

運用環境では、設定ファイルは存在しないので、クライアントはこのマクロの結果を見ることができないことに注意してください...。しかし、必要なときにそれをアクティブにするのは簡単です。

結論

リターンコードを使ってコーディングするとき、あなたは失敗のために自分自身を準備し、テストの要塞が十分に安全であることを望んでいるのです。

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

しかし、コードを書くときは、自由に使える最高のツールを使用する必要があります。そして、時にはそれは「エラーを隠さず、できるだけ早く表示する」ことです。上でお話したマクロはこの哲学に従っています。