なぜ例外を保守的に使用する必要があるのですか?


80

例外はめったに使用されるべきではないと人々が言うのをよく見たり聞いたりしますが、その理由を説明することは決してありません。それは本当かもしれませんが、理論的根拠は通常glibです:「それは理由で例外と呼ばれます」これは私には、立派なプログラマー/エンジニアによって決して受け入れられるべきではない一種の説明のようです。

例外を使用して解決できる問題にはさまざまなものがあります。制御フローにそれらを使用するのが賢明でないのはなぜですか?それらがどのように使用されるかについて非常に保守的である背後にある哲学は何ですか?セマンティクス?パフォーマンス?複雑?美学?コンベンション?

以前にパフォーマンスに関する分析を見たことがありますが、一部のシステムに関連し、他のシステムには関連しないレベルです。

繰り返しになりますが、特別な状況のためにそれらを保存する必要があることに必ずしも同意しませんが、コンセンサスの論理的根拠は何であるか疑問に思っています(そのようなものが存在する場合)。



32
重複していません。リンクされた例は、例外処理がまったく役立つかどうかに関するものです。これは、例外を使用する場合と別のエラー報告メカニズムを使用する場合の違いについてです。
エイドリアンマッカーシー

1
そして、この1つのstackoverflow.com/questions/1385172/…はどうですか?
stijn 2009年

1
コンセンサスの根拠はありません。例外をスローすることの「適切さ」については、人によって意見が異なります。これらの意見は、通常、開発する言語に影響されます。この質問にC ++のタグを付けましたが、Javaのタグを付けると異なる意見が得られると思います。
チャールズサルビア

5
いいえ、ウィキであってはなりません。ここでの回答には専門知識が必要であり、担当者が報われる必要があります。
bobobobo 2010

回答:


92

摩擦の主なポイントはセマンティクスです。多くの開発者は例外を悪用し、あらゆる機会にそれらをスローします。アイデアは、やや例外的な状況に対して例外を使用することです。たとえば、間違ったユーザー入力は例外としてカウントされません。これは、これが発生してその準備ができていることを期待しているためです。しかし、ファイルを作成しようとして、ディスクに十分なスペースがなかった場合は、そうです、これは明確な例外です。

もう1つの問題は、例外が頻繁にスローされて飲み込まれることです。開発者はこの手法を使用して、プログラムを単に「無音」にし、完全に崩壊するまで可能な限り長く実行させます。これは非常に間違っています。例外を処理しない場合、一部のリソースを解放して適切に対応しない場合、例外の発生をログに記録しない場合、または少なくともユーザーに通知しない場合は、例外を使用していません。

あなたの質問に直接答えます。例外的な状況はまれであり、例外は高額であるため、例外を使用することはめったにありません。

まれです。ボタンを押すたびに、または不正な形式のユーザー入力ごとにプログラムがクラッシュするとは思わないためです。たとえば、データベースに突然アクセスできなくなったり、ディスクに十分な容量がなかったり、依存しているサードパーティのサービスがオフラインになったりする場合があります。これはすべて発生する可能性がありますが、非常にまれですが、これらが明らかに例外的なケースです。

例外をスローすると通常のプログラムフローが中断されるため、費用がかかります。ランタイムは、例外を処理できる適切な例外ハンドラーが見つかるまで、スタックを巻き戻します。また、ハンドラーが受け取る例外オブジェクトに渡される途中で呼び出し情報を収集します。それはすべてコストがかかります。

これは、例外を使用することに例外がないということではありません(笑顔)。多くのレイヤーを介してリターンコードを転送する代わりに例外をスローすると、コード構造が単純化される場合があります。簡単なルールとして、あるメソッドが頻繁に呼び出され、半分の時間で「例外的な」状況を発見することが予想される場合は、別の解決策を見つけることをお勧めします。ただし、この「例外的な」状況がまれな状況でのみ発生する可能性があるのに、ほとんどの場合通常の操作の流れが予想される場合は、例外をスローするだけで問題ありません。

@Comments:例外は、コードをより単純で簡単にすることができれば、例外の少ない状況でも確実に使用できます。このオプションはオープンですが、実際には非常にまれだと思います。

制御フローにそれらを使用するのが賢明でないのはなぜですか?

例外は通常の「制御フロー」を混乱させるためです。例外が発生すると、プログラムの通常の実行が中止され、オブジェクトが一貫性のない状態になり、一部のオープンリソースが解放されない可能性があります。確かに、C#にはusingステートメントがあり、using本体から例外がスローされた場合でもオブジェクトが確実に破棄されます。しかし、とりあえず言語から抽象化しましょう。フレームワークがオブジェクトを破棄しないと仮定します。手動で行います。リソースとメモリを要求して解放する方法についてのシステムがあります。どのような状況でオブジェクトとリソースを解放する責任があるかについて、システム全体で合意があります。外部ライブラリの扱い方にはルールがあります。プログラムが通常の操作フローに従っている場合、これはうまく機能します。しかし、実行の途中で突然例外がスローされます。リソースの半分は解放されません。半分はまだ要求されていません。操作がトランザクションであることが意図されていた場合、それは壊れています。リソースの解放を担当するコード部分は単に実行されないため、リソースを処理するためのルールは機能しません。他の誰かがそれらのリソースを使用したい場合、彼らはこの特定の状況を予測できなかったため、それらが一貫性のない状態にあり、同様にクラッシュする可能性があります。

たとえば、メソッドM()がメソッドN()を呼び出して何らかの作業を行い、リソースを配置してから、それをM()に戻し、それを使用して破棄したいとします。結構です。N()で問題が発生し、M()で予期していなかった例外がスローされるため、C()メソッドでキャッチされるまで例外が上部にバブルします。このメソッドは、何が起こっているのかわかりません。 N()で、いくつかのリソースを解放するかどうか、および解放する方法。

例外をスローすることで、プログラムを、予測、理解、および処理するのが難しい多くの新しい予測不可能な中間状態にする方法を作成します。GOTOを使用するのと少し似ています。ある場所から別の場所にランダムに実行をジャンプできるプログラムを設計することは非常に困難です。また、それを維持およびデバッグすることも困難になります。プログラムが複雑になると、それを修正するために、いつ、どこで何が起こっているのかについての概要が失われます。


5
例外をスローする関数とエラーコードを返す関数を比較すると、何かが足りない場合を除いて、スタックの巻き戻しは同じになります。
Catskul 2009年

9
@Catskul:必ずしもそうとは限りません。関数returnは、制御を直接の呼び出し元に直接返します。スローされた例外は、現在の呼び出しスタック全体で、その例外タイプ(またはその基本クラスまたは「...」)の最初のcatchハンドラーに制御を返します。
jon-hanson 2009年

5
デバッガーは、例外が発生するとデフォルトで機能しなくなる場合が多いことにも注意してください。異常ではない条件で例外を使用している場合、デバッガーは大幅に機能しなくなります。
qwertie 2009年

5
@jon:それがキャッチされず、エラー処理に到達するためにより多くのスコープをエスケープする必要がある場合にのみ発生します。同じ場合、戻り値を使用すると(また、複数のスコープを介して渡す必要があります)、同じ量のスタックの巻き戻しが発生します。
Catskul 2009年

3
-1ごめんなさい。例外機能が「意味のある」ものであるかについて話すのは無意味です。これらは単なるメカニズムであり、メモリ不足などの例外的な状況を処理するために使用できます。ただし、他の目的にも使用してはならないという意味ではありません。私が見たいのは、なぜそれらを他の目的に使用すべきでないのかについての説明です。あなたの答えはそれについて少し話しますが、それは例外が「意味がある」ものについての多くの話と混ざり合っています。
j_random_hacker 2009年

61

「例外的な状況で例外をスローする」がglibの答えですが、実際には、それらの状況が何であるかを定義できます。前提条件は満たされているが、事後条件は満たされていません。これにより、エラー処理を犠牲にすることなく、より厳密で、より厳密で、より有用な事後条件を記述できます。それ以外の場合は、例外なく、考えられるすべてのエラー状態を考慮して事後条件を変更する必要があります。

  • 関数を呼び出す前に前提条件が真である必要があります。
  • 事後条件は、関数がを返しに保証するものです。
  • 例外安全性は、例外が関数またはデータ構造の内部整合性にどのように影響するかを示し、多くの場合、外部から渡された動作(たとえば、ファンクター、テンプレートパラメーターのctorなど)を処理します。

コンストラクター

C ++で記述できる可能性のあるすべてのクラスのすべてのコンストラクターについて言えることはほとんどありませんが、いくつかのことがあります。その中で最も重要なのは、構築されたオブジェクト(つまり、コンストラクターが戻ることで成功したオブジェクト)が破棄されることです。 言語はそれが真であると想定し、デストラクタを自動的に呼び出すため、この事後条件を変更することはできません。 (技術的には、言語が何も保証しない未定義の動作の可能性を受け入れることができますが、それはおそらく他の場所でカバーされている方がよいでしょう。)

コンストラクターが成功できない場合に例外をスローする唯一の方法は、クラスの基本定義(「クラス不変」)を変更して、有効な「null」またはゾンビ状態を許可し、コンストラクターがゾンビを作成して「成功」できるようにすることです。 。

ゾンビの例

このゾンビの変更の例はstd :: ifstreamであり、使用する前に常にその状態を確認する必要があります。そのためのstd :: stringは、たとえば、あなたは常にあなたがすぐに建設した後、それを使用することが保証されていません。この例のようなコードを作成する必要があり、ゾンビの状態を確認するのを忘れた場合、サイレントに誤った結果が得られるか、プログラムの他の部分が破損することを想像してみてください。

string s = "abc";
if (s.memory_allocation_succeeded()) {
  do_something_with(s); // etc.
}

そのメソッドに名前を付けることでさえ、クラスの不変条件を変更する必要がある方法の良い例であり、シチュエーション文字列のインターフェイスはそれ自体を予測も処理もできません。

入力例の検証

一般的な例、つまりユーザー入力の検証について説明しましょう。入力の失敗を許可したいからといって、解析関数がそれを事後条件に含める必要があるわけではありません。ただし、ハンドラーがパーサーが失敗したかどうかを確認する必要があることを意味します。

// boost::lexical_cast<int>() is the parsing function here
void show_square() {
  using namespace std;
  assert(cin); // precondition for show_square()
  cout << "Enter a number: ";
  string line;
  if (!getline(cin, line)) { // EOF on cin
    // error handling omitted, that EOF will not be reached is considered
    // part of the precondition for this function for the sake of example
    //
    // note: the below Python version throws an EOFError from raw_input
    //  in this case, and handling this situation is the only difference
    //  between the two
  }
  int n;
  try {
    n = boost::lexical_cast<int>(line);
    // lexical_cast returns an int
    // if line == "abc", it obviously cannot meet that postcondition
  }
  catch (boost::bad_lexical_cast&) {
    cout << "I can't do that, Dave.\n";
    return;
  }
  cout << n * n << '\n';
}

残念ながら、これは、C ++のスコープでRAII / SBRMを破る必要がある方法の2つの例を示しています。その問題がなく、C ++に必要なものを示すPythonの例– try-else:

# int() is the parsing "function" here
def show_square():
  line = raw_input("Enter a number: ") # same precondition as above
  # however, here raw_input will throw an exception instead of us
  # using assert
  try:
    n = int(line)
  except ValueError:
    print "I can't do that, Dave."
  else:
    print n * n

前提条件

前提条件を厳密にチェックする必要はありません。違反すると常に論理障害が発生し、呼び出し元の責任になります。ただし、前提条件をチェックする場合は、例外をスローするのが適切です。(場合によっては、ガベージを返すか、プログラムをクラッシュさせる方が適切です。ただし、これらのアクションは他のコンテキストではひどく間違っている可能があります。未定義の動作を最適に処理する方法は別のトピックです。)

特に、stdlib例外階層のstd :: logic_errorブランチstd :: runtime_errorブランチを対比してください。前者は前提条件違反によく使用されますが、後者は事後条件違反に適しています。


5
まともな答えですが、あなたは基本的にルールを述べているだけであり、理論的根拠を提供していません。それではあなたの合理性はありますか:コンベンションとスタイル?
Catskul 2009年

6
+1。これは、例外が使用されるように設計された方法であり、このように使用すると、コードの可読性と再利用性(IMHO)が実際に向上します。
ダニエルプライデン2009年

15
Catskul:1つ目は例外的な状況の定義であり、理論的根拠や慣習ではありません。事後条件を保証できない場合は、を返すことはできません。例外なく、すべてのエラー状態を含めるために事後条件を非常に広くして、実際には役に立たないようにする必要があります。「なぜ珍しいのか」という文字通りの質問には、そのように見ていなかったので答えなかったようです。これらは、エラーの発生を許容しながら、事後条件を便利に引き締めることができるツールです(例外的な状況では:)。

9
+1。前後の状態は私が今まで聞いた中で最高の説明です。
kitsuneYMG 2009年

6
多くの人が「しかし、それは単に慣習/意味論に帰着する」と言っています。はい、そのとおり。ただし、規則とセマンティクスは、複雑さ、使いやすさ、および保守性に深い影響を与えるため、重要です。結局のところ、事前条件と事後条件を正式に定義するかどうかの決定も、慣例とセマンティクスの問題ではありませんか?それでも、それらを使用すると、コードの使用と保守が大幅に簡単になります。
ダリル

40

  1. カーネル(システム)シグナルインターフェイスを管理するための高価なカーネル呼び出し(または他のシステムAPI呼び出し)
  2. 分析が難しいステートメント
    の問題の多くはgoto例外に当てはまります。多くの場合、複数のルーチンやソースファイルで大量のコードを飛び越えます。これは、中間ソースコードを読んでも必ずしも明らかではありません。(Javaです。)
  3. 中間コードで常に予期されるとは限りません
    ジャンプするコードは、例外終了の可能性を念頭に置いて記述されている場合とされていない場合があります。もともとそのように書かれている場合、それを念頭に置いて維持されていない可能性があります。考えてみてください:メモリリーク、ファイル記述子リーク、ソケットリーク、誰が知っていますか?
  4. メンテナンスの複雑
    さ例外の処理を飛び回るコードをメンテナンスするのは困難です。

24
+1、一般的に正当な理由。しかし、スタックのコストはデストラクタの巻き戻しと呼び出すことに注意してください(少なくとも)がためにテストし、Cのようなエラーコードを返すことによって、例えば、任意の機能的に同等のエラー処理メカニズムによって支払われる
j_random_hacker

j_randomに同意しますが、これを回答としてマークします。スタックの巻き戻しとリソースの割り当て解除/割り当ては、機能的に同等のすべてのメカニズムで同じです。同意する場合は、これが表示されたら、リストからそれらを切り取って、答えを少し良くしてください。
Catskul 2009年

14
ジュリアン:j_randomのコメントは、比較の節約はないと指摘しint f() { char* s = malloc(...); if (some_func() == error) { free(s); return error; } ... }ています。手動で行うか例外を介して行うかにかかわらず、スタックを巻き戻すには料金を支払う必要があります。例外を使用することとエラー処理をまったく行わないことを比較することはできません。

14
1.高価:時期尚早の最適化はすべての悪の根源です。2.分析が難しい:エラーリターンコードのネストされたレイヤーと比較して?私は敬意を表して反対します。3.中間コードによって常に予期されるわけではありません:ネストされたレイヤーと比較して、エラーを常に合理的に処理および変換するとは限りませんか?初心者は両方で失敗します。4.メンテナンスの複雑さ:どのように?エラーコードによって仲介される依存関係は、維持するのが簡単ですか?...しかし、これはどちらの学校の開発者もお互いの議論を受け入れるのが難しい分野の1つであるように思われます。他の場合と同様に、エラー処理の設計はトレードオフです。
Pontus Gagge 2010

1
これは複雑な問題であり、一般論で正確に答えるのは難しいことには同意します。ただし、元の質問は、全体的なベストプラクティスの説明ではなく、単に例外防止のアドバイスが提供された理由を尋ねたものであることに注意してください。
DigitalRoss 2010

22

例外のスローは、ある程度、gotoステートメントに似ています。フロー制御のためにそれを行うと、理解できないスパゲッティコードで終わります。さらに悪いことに、ジャンプが正確にどこに行くのかさえわからない場合もあります(つまり、特定のコンテキストで例外をキャッチしていない場合)。これは、保守性を高める「驚き最小の原則」に露骨に違反しています。


5
フロー制御以外の目的で例外を使用するにはどうすればよいですか?例外的な状況でも、これらはフロー制御メカニズムであり、コードが明確になる結果になります(ここで想定:理解できない)。例外を使用することはできると思いますが、禁止します。catchただし、その場合は、std::terminate()自分自身を呼び出すこともできます。全体として、この議論は、「例外をめったに使用しない」ではなく、「例外を使用しない」と私には思えます。
スティーブジェソップ

5
関数内のreturnステートメントは、関数から抜け出します。例外は、何層上にあるかを知っている人を連れて行きます-それを見つける簡単な方法はありません。

2
スティーブ:私はあなたの主張を理解していますが、それは私が意図したことではありません。予期しない状況でも例外は問題ありません。一部の人々は、おそらくある種のswitchステートメントとしてさえ、それらを「早期リターン」として悪用します。
Erich Kitzmueller 2009年

2
この質問は、「予期しない」が十分に説明できないことを前提としていると思います。ある程度同意します。何らかの条件で関数が希望どおりに完了しない場合は、プログラムがその状況を正しく処理するか、処理しないかのどちらかです。それがそれを処理する場合、それは「期待される」ものであり、コードは理解可能である必要があります。それが正しく処理されない場合、あなたは困っています。例外でプログラムを終了させない限り、コードのあるレベルでそれを「期待」する必要があります。それでも実際には、私の答えで言うように、理論的には不十分であるにもかかわらず、それは良いルールです。
スティーブジェソップ

2
さらに、もちろんラッパーは、スロー関数をエラーを返す関数に、またはその逆に変換できます。したがって、発信者が「予期しない」という考えに同意しない場合でも、必ずしもコードの理解可能性が損なわれるわけではありません。「予期しない」と思われる多くの条件を引き起こしている場合、パフォーマンスがいくらか犠牲になる可能性がありますが、正常で回復可能であると考えているため、キャッチしてerrnoなどに変換します。
スティーブジェソップ

16

例外があると、プログラムの状態について推論するのが難しくなります。たとえば、C ++では、関数が例外的に安全であることを確認するために、必要がない場合よりも特別な検討を行う必要があります。

その理由は、例外なく、関数呼び出しが戻るか、プログラムを最初に終了できるためです。例外を除いて、関数呼び出しは、プログラムを返すか、プログラムを終了するか、どこかのキャッチブロックにジャンプすることができます。そのため、目の前のコードを見るだけでは、制御の流れをたどることはできなくなります。呼び出された関数がスローできるかどうかを知る必要があります。コントロールがどこに行くかを気にするか、それとも現在のスコープを離れることだけを気にするかに応じて、何を投げることができ、どこでキャッチされるかを知る必要があるかもしれません。

このため、「本当に例外的な状況でない限り、例外を使用しないでください」と言われます。「本当に例外的」とは、「エラーの戻り値で処理することのメリットがコストを上回っている状況が発生した」ことを意味します。そうです、これは空のステートメントのようなものですが、「本当に例外的」という本能があれば、それは大まかな目安になります。人々がフロー制御について話すとき、彼らは(キャッチブロックを参照せずに)ローカルで推論する能力が戻り値の利点であることを意味します。

Javaには、C ++よりも「本当に例外的」という広い定義があります。C ++プログラマーは、Javaプログラマーよりも関数の戻り値を見たいと思う傾向があるため、Javaでは、「本当に例外的」とは、「この関数の結果としてnull以外のオブジェクトを返すことができない」ことを意味する場合があります。C ++では、「発信者が続行できるかどうかは非常に疑わしい」という意味になる可能性が高くなります。したがって、Javaストリームはファイルを読み取れない場合にスローしますが、C ++ストリーム(デフォルト)はエラーを示す値を返します。ただし、すべての場合において、呼び出し元に強制的に記述させようとするコードの問題です。したがって、それは確かにコーディングスタイルの問題です。コードがどのように見えるべきか、そしてどのくらいの「例外安全性」に対してどれだけの「エラーチェック」コードを書きたいかについてコンセンサスに達する必要があります。

すべての言語にわたる幅広いコンセンサスは、エラーがどの程度回復可能であるかという観点からこれを行うのが最善であるようです(回復不可能なエラーは例外のあるコードを生成しませんが、それでも自分自身をチェックして返す必要があるため)エラーリターンを使用するコードのエラー)。そのため、人々は「私が呼び出すこの関数は例外をスローする」という意味で「続行できない」は、「それだけでなく、続行できないなります期待するようになります。これは例外に固有のものではなく、単なる習慣ですが、他の優れたプログラミング手法と同様に、他の方法で試しても結果を享受しなかった賢い人々によって提唱された習慣です。私も、あまりにも多くの例外を投げるという悪い経験をしました。個人的には、状況について何かが例外を特に魅力的にしない限り、「本当に例外的」という観点から考えています。

ところで、コードの状態について推論する以外に、パフォーマンスへの影響もあります。パフォーマンスを気にする資格のある言語では、例外は通常安価です。それらは、「ああ、結果はエラーです。それなら、私もエラーで終了するのが最善です」という複数のレベルよりも高速になる可能性があります。古き良き時代には、例外をスローし、それをキャッチし、次のことを続けると、あなたがしていることが役に立たなくなるほど遅くなるのではないかという本当の恐れがありました。したがって、その場合、「本当に例外的」とは、「状況が非常に悪いため、恐ろしいパフォーマンスはもはや問題にならない」という意味です。これはもはや当てはまりません(ただし、タイトループの例外は依然として目立ちます)。うまくいけば、「本当に例外的な」の定義を柔軟にする必要がある理由を示しています。


2
「たとえば、C ++では、関数が例外的に安全であることを確認するために、必要がない場合よりも特別な検討を行う必要があります。」-基本的なC ++構造(などnew)と標準ライブラリがすべて例外をスローすることを考えると、コードで例外を使用しないことで、例外がないコードを作成する必要がなくなるかどうかはわかりません。
Pavel Minaev 2009年

1
あなたはグーグルに尋ねなければならないでしょう。しかし、そうです、要点を言えば、関数がスローされていない場合、呼び出し元はいくつかの作業を行う必要があり、例外を引き起こす条件を追加しても、「例外をスローする」ことにはなりません。しかし、メモリ不足はとにかく少し特殊なケースです。それを処理せず、代わりにシャットダウンするプログラムがあるのは一般的です。「例外を使用しない、例外を保証しない、新しいスローが発生した場合は終了し、スタックを巻き戻さないコンパイラを使用する」というコーディング標準を作成できます。ハイコンセプトではありませんが、飛ぶでしょう。
スティーブジェソップ

スタックを巻き戻さない(またはスタック内の例外を無効にする)コンパイラを使用すると、技術的にはC ++ではなくなります:)
Pavel Minaev 2009年

つまり、キャッチされていない例外でスタックを巻き戻しません。
スティーブジェソップ

スタックを巻き戻してキャッチブロックを見つけない限り、キャッチされていないことをどのようにして知ることができますか?
jmucchiello 2009年

11

本当にコンセンサスはありません。例外をスローすることの「適切さ」は、言語自体の標準ライブラリ内の既存のプラクティスによって示唆されることが多いため、問題全体はやや主観的です。C ++標準ライブラリは、Java標準ライブラリよりもはるかに少ない頻度で例外をスローします。これは、無効なユーザー入力(例)などの予想されるエラーに対しても、ほとんどの場合例外優先しますScanner.nextInt。これは、いつ例外をスローするのが適切かについての開発者の意見に大きく影響すると思います。

C ++プログラマーとして、私は個人的に、メモリ不足、ディスクスペース不足、黙示録の発生など、非常に「例外的な」状況のために例外を予約することを好みます。しかし、これが絶対的に正しい方法であるとは主張しません。物事。


2
ある種のコンセンサスがあると思いますが、おそらくコンセンサスは、健全な推論ではなく、慣習に基づいているのでしょう。例外的な状況でのみ例外を使用する正当な理由があるかもしれませんが、ほとんどの開発者はその理由を実際には認識していません。
qwertie 2009年

4
同意しました-「例外は例外的な状況のためのものです」とよく耳にしますが、「例外的な状況」が何であるかを適切に定義することを誰も気にしません-それは主に単なる習慣であり、間違いなく言語固有です。ちなみに、Pythonでは、イテレータは例外を使用してシーケンスの終了を通知します。これは完全に正常と見なされます。
Pavel Minaev 2009年

2
+1。厳格な規則はなく、規則だけがありますが、プログラマーがお互いのコードを理解しやすくなるため、あなたの言語を使用する他の人の規則に従うと便利です。
j_random_hacker 2009年

1
「黙示録が起こった」-例外を気にしないでください、もし何かがUBを正当化するなら、それはそうします;-)
Steve Jessop

3
Catskul:プログラミングのほとんどすべてが慣例です。技術的には、例外さえ必要ないか、まったく必要ありません。NP完全性、big-O / theta / little-o、またはユニバーサルチューリングマシンが含まれていない場合は、おそらく慣例です。:-)
ケン

7

例外はめったに使用されるべきではないと思います。だが。

すべてのチームとプロジェクトが例外を使用する準備ができているわけではありません。例外を使用するには、プログラマーの高度な資格、特別な技術、および大きなレガシーの例外安全でないコードの欠如が必要です。巨大な古いコードベースがある場合、ほとんどの場合、例外安全ではありません。書き直したくないと思います。

例外を広範囲に使用する場合は、次のようにします。

  • 例外安全性とは何かについて人々に教える準備をしてください
  • 生のメモリ管理を使用しないでください
  • RAIIを幅広く使用する

一方、強力なチームを持つ新しいプロジェクトで例外を使用すると、コードがよりクリーンになり、保守が容易になり、さらに高速になる可能性があります。

  • エラーを見逃したり無視したりすることはありません
  • 低レベルで間違ったコードをどう処理するかを実際に知らずに、リターンコードのチェックを書く必要はありません。
  • 例外安全なコードを書くことを余儀なくされると、それはより構造化されます

1
「すべてのチームが...例外を使用する準備ができているわけではない」という事実についてのあなたの言及が特に気に入りました。例外は確かに簡単に見えるものですが、正しく実行するのは非常に困難であり、それがそれらを危険なものにしている理由の一部です。
j_random_hacker 2009年

1
「例外安全なコードを書くことを余儀なくされると、それはより構造化されます」の+1。例外ベースのコードはより多くの構造を持つことを余儀なくされており、オブジェクトや不変条件を無視できない場合は、それらについて推論する方が実際にははるかに簡単です。実際、強力な例外安全性とは、ほぼ可逆的なコードを作成することであり、不確定な状態を非常に簡単に回避できると私は信じています。
トム

7

2009年1月20編集

マネージコードのパフォーマンスの向上に関するこのMSDNの記事を読んでいたところ、この部分でこの質問を思い出しました。

例外をスローすることによるパフォーマンスコストは重要です。構造化例外処理はエラー状態を処理するための推奨される方法ですが、エラー状態が発生する例外的な状況でのみ例外を使用するようにしてください。通常の制御フローに例外を使用しないでください。

もちろん、これは.NET専用であり、特に高性能アプリケーションを開発している人(私のような)を対象としています。ですから、それは明らかに普遍的な真実ではありません。それでも、私たち.NET開発者はたくさんいるので、注目に値するものだと感じました。

編集

OK、まず第一に、1つのことをまっすぐにしましょう:私はパフォーマンスの質問について誰かとの戦いを選ぶつもりはありません。一般的に、実際、私は時期尚早の最適化が罪であると信じている人々に同意する傾向があります。ただし、2つの点を指摘しておきます。

  1. ポスターは、例外は控えめに使用されるべきであるという一般通念の背後にある客観的な論理的根拠を求めています。読みやすさと適切なデザインについて、私たちが望むすべてについて話し合うことができます。しかし、これらはどちらの側でも議論する準備ができている人々の主観的な問題です。ポスターはこれを知っていると思います。実際のところ、例外を使用してプログラムフローを制御することは、多くの場合、非効率的な方法です。いいえ、常にではありませんが、多くの場合。これが、赤身の肉を食べたりワインを控えめに飲んだりするのと同じように、例外を控えめに使用することが合理的なアドバイスである理由です。

  2. 正当な理由のない最適化と効率的なコードの記述には違いがあります。これに対する当然の結果として、最適化されていなくても堅牢なものを書くことと、単に非効率的なものを書くことには違いがあります。例外処理のようなことについて議論するとき、彼らは根本的に異なることを話し合っているので、実際にはお互いを超えて話しているだけだと思う​​ことがあります。

私のポイントを説明するために、次のC#コード例を検討してください。

例1:無効なユーザー入力の検出

これは、私が例外の乱用と呼ぶものの例です。

int value = -1;
string input = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        value = int.Parse(input);
        inputChecksOut = true;

    } catch (FormatException) {
        input = GetInput();
    }
}

このコードは、私にはばかげています。もちろん動作します。誰もそれについて議論していません。しかし、それ次のようなものでなければなりません:

int value = -1;
string input = GetInput();

while (!int.TryParse(input, out value)) {
    input = GetInput();
}

例2:ファイルの存在を確認する

このシナリオは実際には非常に一般的だと思います。ファイルI / Oを処理するため、多くの人にとっては確かにはるかに「受け入れられる」ように思われます。

string text = null;
string path = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        using (FileStream fs = new FileStream(path, FileMode.Open)) {
            using (StreamReader sr = new StreamReader(fs)) {
                text = sr.ReadToEnd();
            }
        }

        inputChecksOut = true;

    } catch (FileNotFoundException) {
        path = GetInput();
    }
}

これは十分に合理的なようですよね?ファイルを開こうとしています。そこにない場合は、その例外をキャッチして別のファイルを開こうとします...何が問題なのですか?

本当に何もありません。ただし、例外をスローしないこの代替案を検討してください。

string text = null;
string path = GetInput();

while (!File.Exists(path)) path = GetInput();

using (FileStream fs = new FileStream(path, FileMode.Open)) {
    using (StreamReader sr = new StreamReader(fs)) {
        text = sr.ReadToEnd();
    }
}

もちろん、これら2つのアプローチのパフォーマンスが実際に同じである場合、これは実際には純粋に教義上の問題になります。それでは、見てみましょう。最初のコード例では、10000個のランダムな文字列のリストを作成しましたが、いずれも適切な整数を表していないため、最後に有効な整数文字列を追加しました。上記の両方のアプローチを使用して、これらは私の結果でした:

使用try/catchブロック:25.455
使用int.TryParse:1.637ミリ秒

2番目の例では、基本的に同じことを行いました。10000個のランダムな文字列のリストを作成しましたが、いずれも有効なパスではなく、最後に有効なパスを追加しました。結果は次のとおりです。

使用try/catchブロック:29.989
使用File.Exists:22.820ミリ秒

多くの人がこれに「そうですね、10,000の例外をスローしてキャッチすることは非常に非現実的です。これは、結果を誇張します」と答えます。もちろんそうです。1つの例外をスローすることと、自分で不正な入力を処理することの違いは、ユーザーには気付かれません。例外の使用は、これら2つのケースでは、同じように読みやすい代替アプローチより1,000倍から10,000倍以上遅くなるという事実が残っています。

そのため、GetNine()以下の方法の例を含めました。それが耐えられないほど遅い、または容認できないほど遅いということではありません。本来よりも遅いということです...理由はありません

繰り返しますが、これらは2つの例にすぎません。うちもちろん例外を使用してのパフォーマンスヒットはこの厳しい(;すべての後に、それは実装に依存しないパベルの権利)でない時があります。私が言っているのは、事実に直面しましょう。上記のような場合、例外をスローしてキャッチすることは、GetNine();に類似しています。これは、簡単に改善できることを行うための非効率的な方法です。


あなたは、これが理由を知らずに誰もが時流に乗ったような状況の1つであるかのように論理的根拠を求めています。しかし実際には答えは明白であり、あなたはすでにそれを知っていると思います。例外処理には、ひどいパフォーマンスがあります。

OK、特にビジネスシナリオには問題ないかもしれませんが、比較的言えば、例外をスロー/キャッチすると、多くの場合、必要以上のオーバーヘッドが発生します。あなたはそれを知っています、私はそれを知っています:ほとんどの場合、プログラムの流れを制御するために例外を使用しているなら、あなたはただ遅いコードを書いているだけです。

あなたは尋ねたほうがいいかもしれません:なぜこのコードは悪いのですか?

private int GetNine() {
    for (int i = 0; i < 10; i++) {
        if (i == 9) return i;
    }
}

この関数のプロファイルを作成すると、通常のビジネスアプリケーションで非常に高速に実行されることがわかると思います。それは、それがはるかに良くできることを達成するためのひどく非効率的な方法であるという事実を変えません。

それが、例外的な「虐待」について話すときの人々の意味です。


2
「例外処理にはひどいパフォーマンスがあります。」-これは実装の詳細であり、すべての言語、特にC ++、特にすべての実装に当てはまるわけではありません。
Pavel Minaev 2009年

「これは実装の詳細です」この議論をどれほど頻繁に聞いたか思い出せません-そしてそれはただ悪臭を放ちます。覚えておいてください:すべてのコンピューター関連のものは、実装の詳細の合計に関するものです。また、ハンマーの代わりにスクリュードライバーを使用することは賢明ではありません。これは、「実装の詳細」について多くのことを話している人々が行うことです。
ユルゲン

2
それでも、Pythonは、一般的なフロー制御の例外を喜んで使用します。これは、実装のパフォーマンスヒットがそれほど悪くないためです。ハンマーがドライバーのように見える場合は、ハンマーのせいにしてください...
Pavel Minaev 2009年

1
@j_random_hacker:GetNineその通りです、その仕事はうまくやっています。私のポイントは、それを使用する理由がないということです。それは何かを成し遂げるための「迅速で簡単な」方法ではありませんが、より「正しい」アプローチはより多くの努力を必要とします。私の経験では、「正しい」アプローチは実際にはそれほど労力を要しないか、ほぼ同じであることがよくあります。とにかく、私にとってパフォーマンスは、左右の例外を使用することが推奨されない唯一の客観的な理由です。パフォーマンスの低下が「大幅に過大評価」されているかどうかについては、実際には主観的です。
ダン・タオ

1
@j_random_hacker:あなたの脇は非常に興味深いものであり、実装に応じてパフォーマンスが低下するというPavelの主張を実際に示しています。私が非常に疑うことの1つは、例外の使用が実際に代替案よりも優れているシナリオを誰かが見つける可能性があることです(少なくとも私の例のように、代替案が存在する場合)。
ダン・タオ

6

例外に関する経験則はすべて、主観的な用語に帰着します。いつ使用するか、いつ使用しないかについて、厳密かつ迅速に定義することを期待するべきではありません。「例外的な状況でのみ」。良い循環定義:例外は例外的な状況のためのものです。

例外を使用する場合は、「このコードが1つのクラスか2つのクラスかをどのように知ることができますか?」と同じバケットに分類されます。これは、一部はスタイルの問題であり、一部は好みです。例外はツールです。それらは使用および悪用される可能性があり、2つの間の境界線を見つけることはプログラミングの芸術とスキルの一部です。

多くの意見があり、トレードオフが必要です。あなたに話しかける何かを見つけて、それに従ってください。


6

例外がめったに使用されるべきではないというわけではありません。例外的な状況でのみスローする必要があるというだけです。たとえば、ユーザーが間違ったパスワードを入力した場合、それは例外ではありません。

理由は単純です。例外が関数を突然終了し、スタックをcatchブロックに伝播します。このプロセスは非常に計算コストが高くなります。C++は「通常の」関数呼び出しのオーバーヘッドがほとんどないように例外システムを構築するため、例外が発生すると、どこに行くかを見つけるために多くの作業を行う必要があります。さらに、コードのすべての行で例外が発生する可能性があるためです。f例外を頻繁に発生させる関数がある場合は、のすべての呼び出しでtry/catchブロックを使用するように注意する必要がありますf。これは、インターフェイスと実装の結合がかなり悪いです。


8
あなたは本質的に「理由から例外と呼ばれる」という論理的根拠を繰り返してきました。これをより充実したものに肉付けしてくれる人を探しています。
Catskul 2009年

4
「例外的な状況」とはどういう意味ですか?実際には、私のプログラムは、不正なユーザーパスワードよりも頻繁に実際のIOExceptionを処理する必要があります。それは、私BadUserPasswordを例外にするべきだと思うのか、それともstdlibの人たちがIOExceptionを例外にするべきではなかったということですか?「非常に計算コストが高い」というのは実際の理由ではありません。制御メカニズムを介して誤ったパスワードを処理することがパフォーマンスのボトルネックとなったプログラム(そして確かに私のものではない)を見たことがないからです。
ケン

5

この問題については、C ++の例外に関する記事で触れました。

関連する部分:

ほとんどの場合、例外を使用して「通常の」フローに影響を与えることは悪い考えです。セクション3.1ですでに説明したように、例外は非表示のコードパスを生成します。これらのコードパスは、エラー処理シナリオでのみ実行される場合、ほぼ間違いなく受け入れられます。ただし、他の目的で例外を使用する場合、「通常の」コード実行は表示部分と非表示部分に分割され、コードの読み取り、理解、拡張が非常に困難になります。


1
ほら、私はCからC ++に来ましたが、「エラー処理シナリオ」は正常だと思います。それらは頻繁に実行されないかもしれませんが、私はエラー以外のコードパスよりもそれらについて考えることに多くの時間を費やしています。繰り返しになりますが、私は一般的に感情に同意しますが、「通常」の定義に近づくことはできず、「通常」の定義から例外を使用することは悪いオプションであるとどのように推測できますか。
スティーブジェソップ

私もCからC ++に来ましたが、CIでも、「通常の」フロー終了エラー処理を区別していました。ie fopen()を呼び出すと、成功の場合を処理する「正常」なブランチと、考えられる失敗の原因を処理するブランチがあり、それがエラー処理です。
Nemanja Trifunovic

2
そうですが、エラーコードについて推論するときに不思議なことに賢くはなりません。したがって、「見えないコードパス」を読み、理解し、「通常の」コードに拡張するのが非常に難しい場合、「エラー」という言葉を適用すると「間違いなく受け入れられる」理由がまったくわかりません。私の経験では、エラー状況は常に発生し、これにより「正常」になります。エラー状況で例外を把握できる場合は、エラー以外の状況で例外を把握できます。できない場合はできません。エラーコードを解読できないようにするだけですが、「エラーのみ」であるため、その事実は無視されます。
スティーブジェソップ

1
例:何かを実行する必要がある関数がありますが、さまざまなアプローチをすべて試しますが、どれが成功するかわからず、それぞれが追加のサブアプローチを試す可能性があります。など、何かが機能するまで。次に、失敗は「正常」であり、成功は「例外的」であると主張しますが、明らかにエラーではありません。成功時に例外をスローすることは、恐ろしいエラーに例外をスローすることよりも理解するのが難しいことではありません。ほとんどのプログラムでは、コードの1ビットだけが次に何をすべきかを知る必要があることを確認し、そこに直接ジャンプします。
スティーブジェソップ

1
@onebyone:あなたの2番目のコメントは、私が他の場所で行われたのを見たことがない良い点を実際に示しています。それに対する答えは、(あなたがあなた自身の答えで効果的に言ったように)エラー条件の例外を使用することは多くの言語の「標準的な慣行」に吸収されており、元の理由があったとしても遵守するための有用なガイドラインになっていると思いますそれをすることは誤った方向に進んだ。
j_random_hacker 2009年

5

エラー処理に対する私のアプローチは、3つの基本的なタイプのエラーがあるということです。

  • エラーサイトで処理できる奇妙な状況。これは、ユーザーがコマンドラインプロンプトで無効な入力を入力した場合に発生する可能性があります。正しい動作は、単にユーザーに文句を言って、この場合はループすることです。別の状況は、ゼロ除算である可能性があります。これらの状況は実際にはエラー状況ではなく、通常は入力の誤りが原因です。
  • 前のような状況ですが、エラーサイトでは処理できません。たとえば、ファイル名を取得してその名前のファイルを解析する関数がある場合、ファイルを開くことができない可能性があります。この場合、エラーに対処できません。これは例外が光るときです。Cアプローチを使用する(フラグとして無効な値を返し、問題を示すためにグローバルエラー変数を設定する)のではなく、コードは代わりに例外をスローできます。呼び出し元のコードは、例外を処理できるようになります。たとえば、ユーザーに別のファイル名の入力を求めることができます。
  • 起こってはならない状況。これは、クラス不変条件に違反した場合、または関数が無効なパラメーターなどを受け取った場合です。これは、コード内の論理障害を示しています。障害のレベルに応じて、例外が適切な場合もあれば、(そうであるように)即時終了を強制することが望ましい場合もありますassert。一般に、これらの状況は、コードのどこかで何かが壊れていることを示しており、他のものが正しいとは事実上信頼できません。メモリの破損が横行している可能性があります。あなたの船は沈んでいます、降りてください。

言い換えると、例外は、対処できる問題があるが、気付いた場所では対処できない場合です。対処できない問題は、単にプログラムを強制終了する必要があります。すぐに対処できる問題は、単に対処する必要があります。


2
あなたは間違った質問に答えています。エラーシナリオの処理に例外の使用を検討する必要がある(またはすべきでない)理由を知りたくありません。エラー処理以外のシナリオに例外を使用する必要がある(または使用しない)理由を知りたいのです。
j_random_hacker 2009年

5

私はここでいくつかの答えを読みました。私はまだこのすべての混乱が何であるかについて驚いています。私はこのすべての例外== spagettyコードに強く同意しません。混乱して、C ++の例外処理を好まない人がいるということです。C ++の例外処理についてどのように学んだかはわかりませんが、数分以内にその影響を理解しました。これは1996年頃で、私はOS / 2用のborlandC ++コンパイラを使用していました。いつ例外を使用するかを決めるのに問題はありませんでした。私は通常、フォールブルな元に戻すアクションをC ++クラスにラップします。このような元に戻すアクションには、次のものがあります。

  • システムハンドルの作成/破棄(ファイル、メモリマップ、WIN32 GUIハンドル、ソケットなど)
  • ハンドラーの設定/設定解除
  • メモリの割り当て/割り当て解除
  • ミューテックスの要求/解放
  • 参照カウントのインクリメント/デクリメント
  • ウィンドウの表示/非表示

機能的なラッパーがあるより。システムコール(前者のカテゴリに分類されない)をC ++にラップすること。たとえば、ファイルからの読み取り/ファイルへの書き込み。何かが失敗した場合、エラーに関する完全な情報を含む例外がスローされます。

次に、障害にさらに情報を追加するための例外のキャッチ/再スローがあります。

全体的なC ++例外処理は、よりクリーンなコードにつながります。コードの量が大幅に削減されます。最後に、コンストラクターを使用してフォールブルリソースを割り当て、そのような障害が発生した後も破損のない環境を維持できます。

このようなクラスを複雑なクラスにチェーンすることができます。あるメンバー/ベースオブジェクトのコンストラクターが実行されると、同じオブジェクト(以前に実行された)の他のすべてのコンストラクターが正常に実行されたことを信頼できます。


3

例外は、従来の構造(ループ、if、関数など)と比較して、非常に珍しいフロー制御方法です。通常の制御フロー構造(ループ、if、関数呼び出しなど)は、すべての通常の状況を処理できます。日常的に発生する例外に到達した場合は、コードの構造を検討する必要があります。

ただし、通常の構成では簡単に処理できない特定のタイプのエラーがあります。壊滅的な障害(リソース割り当ての失敗など)は低レベルで検出できますが、おそらくそこでは処理できないため、単純なifステートメントでは不十分です。これらのタイプの障害は、通常、はるかに高いレベルで処理する必要があります(たとえば、ファイルの保存、エラーのログ記録、終了)。従来の方法(戻り値など)を使用してこのようなエラーを報告しようとすると、面倒でエラーが発生しやすくなります。さらに、この奇妙で異常な障害を処理するために、中間レベルのAPIのレイヤーにオーバーヘッドを注入します。オーバーヘッドにより、これらのAPIのクライアントの注意がそらされ、制御できない問題について心配する必要があります。例外は、大きなエラーに対して非ローカル処理を行う方法を提供します。

クライアントが電話した場合 ParseInt文字列を使用して、文字列に整数が含まれていない場合、直接の呼び出し元はおそらくエラーを気にし、それに対して何をすべきかを知っています。したがって、ParseIntは、そのようなものの失敗コードを返すように設計します。

一方、 ParseIntメモリがひどく断片化されているためにバッファを割り当てることができなかったために失敗した、呼び出し元はそれについて何をすべきかを知ることができません。この異常なエラーを、これらの基本的な障害を処理するいくつかのレイヤーまでバブルする必要があります。それはその間のすべての人に負担をかけます(彼らは彼ら自身のAPIでエラーパッシングメカニズムに対応しなければならないからです)。例外により、これらのレイヤーをスキップすることができます(必要なクリーンアップが確実に行われるようにします)。

低レベルのコードを記述している場合、従来のメソッドをいつ使用するか、いつ例外をスローするかを決めるのは難しい場合があります。低レベルのコードが決定を下す必要があります(スローするかどうか)。しかし、何が期待され、何が例外的であるかを本当に知っているのは、より高いレベルのコードです。


1
それでも、Javaでは、parseInt実際に例外スローされます。したがって、例外をスローすることの適切性についての意見は、選択した開発言語の標準ライブラリ内の既存のプラクティスに大きく依存していると言えます。
チャールズサルビア

Javaのランタイムはとにかく情報を追跡する必要があるため、例外を簡単に生成できます... c ++コードはその情報を手元に持たない傾向があるため、生成するとパフォーマンスが低下します。手元に置いていないことで、より速く/より小さく/よりキャッシュフレンドリーになる傾向があります。また、Javaコードには、c ++に固有ではない機能である配列などの動的チェックがあるため、開発者がすでにtry / catchブロックでこれらの多くのことを処理しているので、すべてに例外を使用してみませんか?
Ape-in​​ago

1
@ Charles-質問はC ++でタグ付けされているので、私はその観点から答えていました。Java(またはC#)プログラマーが、例外は例外的な条件のためだけであると言うのを聞いたことがありません。C ++プログラマーはこれを定期的に言いますが、問題はその理由についてでした。
エイドリアンマッカーシー

1
実際、C#の例外もそう言っています。その理由は、例外を.NETでスローするのに非常にコストがかかるためです(Javaなどよりもコストがかかります)。
Pavel Minaev 2009年

1
@Adrian:確かに、質問はC ++でタグ付けされていますが、言語は確かに相互に影響を与えるため、例外処理の哲学は言語間の対話のようなものです。とにかく、Boost.lexical_castは例外をスローします。しかし、無効な文字列は本当に「例外的」なのでしょうか。おそらくそうではありませんが、Boost開発者は、そのクールなキャスト構文を持つことは例外を使用する価値があると考えました。これは、全体がどれほど主観的であるかを示しています。
チャールズサルビア

3

C ++にはいくつかの理由があります。

まず、例外がどこから来ているのかを確認するのは難しいことがよくあります(ほとんどすべてのものからスローされる可能性があるため)。したがって、catchブロックはCOMEFROMステートメントのようなものです。GO TOでは、どこから来ているのか(ランダムな関数呼び出しではなく、ステートメント)とどこに行くのか(ラベル)がわかっているため、GOTOよりも悪いです。これらは基本的に、Cのsetjmp()およびlongjmp()の潜在的にリソースセーフなバージョンであり、誰もそれらを使用したくありません。

次に、C ++にはガベージコレクションが組み込まれていないため、リソースを所有するC ++クラスはデストラクタでガベージコレクションを削除します。したがって、C ++例外処理では、システムはスコープ内のすべてのデストラクタを実行する必要があります。GCがあり、Javaのように実際のコンストラクターがない言語では、例外をスローすることははるかに負担が少なくなります。

第三に、Bjarne Stroustrup、標準化委員会、およびさまざまなコンパイラの作成者を含むC ++コミュニティは、例外は例外的であるべきだと想定してきました。一般的に、言語文化に反対する価値はありません。実装は、例外がまれであるという仮定に基づいています。より良い本は例外を例外として扱います。優れたソースコードは、いくつかの例外を使用します。優れたC ++開発者は、例外を例外として扱います。それに反対するために、あなたは正当な理由を望むでしょう、そして私が見るすべての理由はそれらを例外的に保つ側にあります。


1
「C ++にはガベージコレクションが組み込まれていないため、リソースを所有するC ++クラスはデストラクタでガベージコレクションを削除します。したがって、C ++例外処理では、システムはスコープ内のすべてのデストラクタを実行する必要があります。GCがあり、実際のコンストラクタがない言語では、Javaのように、例外をスローすることははるかに負担が少ないです。」-スコープを正常に終了するときにデストラクタを実行する必要があります。Javafinallyブロックは、実装のオーバーヘッドの点でC ++デストラクタと何ら変わりはありません。
Pavel Minaev 2009年

私はこの答えが好きですが、Pavelのように、2番目のポイントは正当ではないと思います。他のタイプのエラー処理やプログラムの続行など、何らかの理由でスコープが終了した場合、とにかくデストラクタが呼び出されます。
Catskul 2009年

流れに乗る理由として言語文化に言及するための+1。その答えは一部の人には満足のいくものではありませんが、それは本当の理由です(そして私はそれが最も正確な理由だと信じています)。
j_random_hacker 2009年

@Pavel:Javafinallyブロックが実行されることが保証されていないという上記の誤ったコメント(現在は削除されています)は無視してください-もちろん実行されます。finalize()実行が保証されていないJavaメソッドと混同されていました。
j_random_hacker 2009年

この答えを振り返ると、デストラクタの問題は、関数が返されるたびに間隔を空けるのではなく、すべてをすぐに呼び出す必要があることです。それはまだあまり良い理由ではありませんが、それは少し妥当性があると思います。
David Thornley 2010

2

これは、制御フローとして例外を使用する悪い例です。

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   try {
      totalIncome= calculateIncomeAsTypeA();
   } catch (IncorrectIncomeTypeException& e) {
      totalIncome= calculateIncomeAsTypeB();
   }

   return totalIncome;
}

これは非常に悪いですが、あなたは書くべきです:

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   if (incomeType == A) {
      totalIncome= calculateIncomeAsTypeA();
   } else if (incomeType == B) {
      totalIncome= calculateIncomeAsTypeB();
   }
   return totalIncome;
}

この2番目の例では、明らかに(デザインパターン戦略の使用など)リファクタリングが必要ですが、例外が制御フローを対象としていないことをよく示しています。

例外にはいくつかのパフォーマンスペナルティも関連付けられていますが、パフォーマンスの問題は次のルールに従う必要があります:「時期尚早の最適化はすべての悪の根源です」


チェックする代わりに、より多くのポリモーフィズムが良い場合のように見えincomeTypeます。
サラ船

@サラはい私はそれが良いだろうと知っています。説明のみを目的としてここにあります。
エジソングスタボムエンツ2009年

3
なぜ悪いのですか?なぜあなたはそれを2番目の方法で書くべきですか?質問者は、ルールではなく、ルールの理由を知りたいと思っています。-1。
j_random_hacker 2009年

2
  1. 保守性:上記の人々が述べたように、帽子をかぶったときに例外をスローすることは、gotoを使用することに似ています。
  2. 相互運用性:例外を使用している場合、C ++ライブラリをC / Pythonモジュールとインターフェースすることはできません(少なくとも簡単ではありません)。
  3. パフォーマンスの低下:RTTIは、追加のオーバーヘッドを課す例外のタイプを実際に見つけるために使用されます。したがって、例外は、一般的に発生するユースケース(ユーザーが文字列の代わりにintを入力したなど)の処理には適していません。

2
1.しかし、一部の言語では、例外一瞬でスローされます。3.パフォーマンスが重要でない場合があります。(時間の80%?)
UncleBens 2009年

2
2. C ++プログラムはほとんどの場合例外を使用します。これは、標準ライブラリの多くが例外を使用しているためです。コンテナクラスはスローします。文字列クラスがスローされます。ストリームクラスがスローします。
デビッドソーンリー

を除いて、どのコンテナおよび文字列操作がスローされat()ますか?そうは言っても、誰でもnew投げることができるので...
Pavel Minaev 2009年

私が理解している限り、RTTIはTry / Throw / Catchに厳密に必要というわけではありません。パフォーマンスの低下は常に言及されており、私はそれを信じていますが、スケールや参照へのリンクについては誰も言及していません。
Catskul 2009年

UncleBens:(1)は、パフォーマンスではなく保守性のためのものです。try / catch / throwを過度に使用すると、コードの可読性が低下します。経験則(そして私はこれについて非難されるかもしれませんが、それは私の意見です)、入口と出口のポイントが少ないほど、コードを読みやすくなります。David Thornley:相互運用性が必要なときではありません。-gccのfno-exceptionsフラグは、Cコードから呼び出されるライブラリをコンパイルするときに例外を無効にします。
Sridhar Iyer

2

例外は、安全な方法で現在のコンテキストから(最も単純な意味では現在のスタックフレームから外れますが、それだけではありません)抜け出すためのメカニズムであると言えます。これは、構造化プログラミングが後藤に最も近いものです。意図された方法で例外を使用するには、現在行っていることを続行できず、現在の時点でそれを処理できない状況が必要です。したがって、たとえば、ユーザーのパスワードが間違っている場合は、falseを返すことで続行できます。ただし、UIサブシステムがユーザーにプロンプ​​トを表示することすらできないと報告した場合、単に「ログインに失敗しました」と返すのは誤りです。現在のレベルのコードは、単に何をすべきかを知りません。そのため、例外メカニズムを使用して、何をすべきかを知っている可能性のある上記の誰かに責任を委任します。


2

非常に実用的な理由の1つは、プログラムをデバッグするときに、アプリケーションをデバッグするためにFirst Chance Exceptions(Debug-> Exceptions)をオンにすることがよくあることです。多くの例外が発生している場合、どこが「間違っている」のかを見つけるのは非常に困難です。

また、悪名高い「キャッチスロー」のようないくつかのアンチパターンにつながり、実際の問題をわかりにくくします。詳細については、この件に関して私が作成したブログ投稿を参照してください。


2

例外はできるだけ使用しないほうがいいです。例外により、開発者は実際のエラーである場合とそうでない場合がある条件を処理する必要があります。問題の例外が致命的な問題なのか、それともしなければならない問題なのかという定義すぐに処理が。

それに対する反論は、怠惰な人々が自分の足で自分自身を撃つためにもっとタイプする必要があるということです。

Googleのコーディングポリシーでは、例外を使用ないように定められています、特にC ++ではれています。アプリケーションは、例外を処理する準備ができていないか、準備ができています。そうでない場合、例外はおそらくアプリケーションが死ぬまでそれを伝播します。

スロー例外を使用したライブラリを見つけるのは決して楽しいことではなく、それらを処理する準備ができていませんでした。


1

例外をスローする正当なケース:

  • ファイルを開こうとしましたが、そこにありません。FileNotFoundExceptionがスローされます。

違法なケース:

  • ファイルが存在しない場合にのみ何かを実行したい場合は、ファイルを開こうとしてから、catchブロックにコードを追加します。

アプリケーションのフローを特定のポイントまで中断したい場合は、例外を使用します。このポイントは、その例外のcatch(...)が存在する場所です。たとえば、大量のプロジェクトを処理する必要があることは非常に一般的であり、各プロジェクトは他のプロジェクトとは独立して処理する必要があります。したがって、プロジェクトを処理するループにはtry ... catchブロックがあり、プロジェクトの処理中に何らかの例外がスローされると、そのプロジェクトのすべてがロールバックされ、エラーがログに記録され、次のプロジェクトが処理されます。人生は続く。

存在しないファイルや無効な式などには例外を使うべきだと思います。 範囲テスト/データ型テスト/ファイルの存在/他に簡単で安価な代替手段がある場合は、例外を使用しないでください。 この種のロジックはコードを理解しにくくするため、範囲テスト/データ型テスト/ファイルの存在/それに代わる簡単で安価な代替手段がある場合は、例外を使用しないでください。

RecordIterator<MyObject> ri = createRecordIterator();
try {
   MyObject myobject = ri.next();
} catch(NoSuchElement exception) {
   // Object doesn't exist, will create it
}

これはより良いでしょう:

RecordIterator<MyObject> ri = createRecordIterator();
if (ri.hasNext()) {
   // It exists! 
   MyObject myobject = ri.next();
} else {
   // Object doesn't exist, will create it
}

回答に追加されたコメント:

たぶん私の例はあまり良くありませんでした-2番目の例ではri.next()が例外をスローするべきではありません。もしそうなら、本当に例外的な何かがあり、他の場所で他のアクションを実行する必要があります。例1が頻繁に使用される場合、開発者は特定の例外ではなく一般的な例外をキャッチし、例外が予期したエラーによるものであると想定しますが、他の原因による可能性があります。結局、例外はアプリケーションフローの一部になり、例外ではないため、実際の例外は無視されます。

これに関するコメントは、私の答え自体以上のものを追加するかもしれません。


1
何故なの?あなたが言及した2番目のケースに例外を使用してみませんか?質問者は、ルールではなく、ルールの理由を知りたいと思っています。
j_random_hacker 2009年

詳しく説明していただきありがとうございますが、私見では、2つのコードスニペットの複雑さはほぼ同じです。どちらも高度にローカライズされた制御ロジックを使用しています。例外の複雑さがそれ/ then / elseの複雑さを最も明らかに超えているのは、tryブロック内に多数のステートメントがあり、そのうちのいずれかがスローされる可能性がある場合です-同意しますか?
j_random_hacker 2009年

たぶん私の例はあまり良くありませんでした-2番目の例ではri.next()が例外をスローするべきではありません。もしそうなら、本当に例外的な何かがあり、他の場所で他のアクションを実行する必要があります。例1が頻繁に使用される場合、開発者は特定の例外ではなく一般的な例外をキャッチし、例外が予期したエラーによるものであると想定しますが、他の原因による可能性があります。結局、例外はアプリケーションフローの一部になり、例外ではないため、実際の例外は無視されます。
Ravi Wallau

つまり、時間の経過とともに、他のステートメントがtryブロック内に蓄積する可能性があり、catchブロックが実際にキャッチしていると思っていたものを実際にキャッチしていることを確信できなくなります-そうですか?if / then / elseアプローチを同じように誤用するのは難しいですが、一度にテストできるのは一度に1つのものだけであり、例外的なアプローチではコードがより脆弱になる可能性があるためです。もしそうなら、あなたの答えでこれについて話し合ってください。コードの脆弱性が誠実な理由であると思うので、私は喜んで+1します。
j_random_hacker 2009年

0

基本的に、例外は構造化されておらず、フロー制御の形式を理解するのが困難です。これは、通常のプログラムフローの一部ではないエラー状態を処理するときに必要です。これは、エラー処理ロジックがコードの通常のフロー制御を乱雑にしないようにするためです。

IMHO例外は、呼び出し元がエラー処理コードの記述を怠った場合、またはエラーが直接の呼び出し元よりもコールスタックの上位で処理される可能性がある場合に、適切なデフォルトを提供する場合に使用する必要があります。正しいデフォルトは、妥当な診断エラーメッセージを表示してプログラムを終了することです。非常識な代替策は、プログラムが誤った状態で足を引きずり、クラッシュするか、後で静かに悪い出力を生成し、ポイントを診断するのが難しくなることです。「エラー」がプログラムフローの通常の部分であり、呼び出し元がそれをチェックすることを合理的に忘れることができない場合は、例外を使用しないでください。


0

「めったに使わない」というのは正しい文章ではないと思います。私は「例外的な状況でのみ投げる」ことを好みます。

多くの人が、なぜ例外を通常の状況で使用すべきではないのかを説明しています。例外には、エラー処理と純粋にエラー処理の権利があります。

私は他の点に焦点を当てます:

もう1つは、パフォーマンスの問題です。コンパイラーはそれらを高速化するのに長い間苦労しました。現在の正確な状態はわかりませんが、制御フローに例外を使用すると、他の問題が発生します。プログラムが遅くなります。

その理由は、例外は非常に強力なgotoステートメントであるだけでなく、離れるすべてのフレームのスタックを巻き戻す必要があるためです。したがって、暗黙的にスタック上のオブジェクトも分解する必要があります。したがって、それを意識せずに、例外を1回スローするだけで、実際には多くのメカニズムが関与することになります。プロセッサは非常に多くのことをしなければなりません。

そのため、知らないうちにプロセッサをエレガントに焼き付けることになります。

したがって、例外は例外的な場合にのみ使用してください-意味:実際のエラーが発生したとき!


1
「その理由は、例外は非常に強力なgotoステートメントであるだけでなく、それらが残すすべてのフレームのスタックを巻き戻す必要があるためです。したがって、暗黙的にスタック上のオブジェクトも分解する必要があります。」-コールスタックで複数のスタックフレームをダウンしている場合、それらのスタックフレーム(およびそれらのオブジェクト)はすべて、いずれにせよ最終的に破棄する必要があります-正常に戻ったため、またはスローしたために発生したかどうかは関係ありません例外。結局、呼び出すstd::terminate()ことを除いて、あなたはまだ破壊しなければなりません。
Pavel Minaev 2009年

もちろんあなたは正しいです。ただし、それでも覚えておいてください。例外を使用するたびに、システムはこれらすべてを魔法のように実行するためのインフラストラクチャを提供する必要があります。それは後藤だけではないことを少し明確にしたかっただけです。また、巻き戻し時に、システムは適切なキャッチ場所を見つける必要があります。これには、余分な時間、コード、およびRTTIの使用が必要になります。ですから、それはジャンプ以上のものです-ほとんどの人はこれを理解していないだけです。
ユルゲン

標準準拠のC ++に固執する限り、RTTIは避けられません。そうでなければ、もちろんオーバーヘッドがあります。それは私がしばしばそれがかなり誇張されているのを見るということだけです。
Pavel Minaev 2009年

1
-1。「例外には、エラー処理と純粋にエラー処理の権利があります」-誰が言いますか?どうして?パフォーマンスだけが理由ではありません。(A)世界のほとんどのコードは、ゲームレンダリングエンジンまたは行列乗算関数の最も内側のループにないため、例外を使用しても、知覚できるパフォーマンスの違いはありません。(B)どこかのコメントで一人が指摘したように、古いCスタイルのエラー戻り値のチェックとパスバックが必要な場合のエラーであっても、スタックの巻き戻しは最終的にはすべて実行する必要があります-処理アプローチが使用されました。
j_random_hacker 2009年

私に言ってください。このスレッドでは、多くの理由が引用されています。それらを読んでください。1つだけ説明したかった。それがあなたが必要としたものでない場合でも、私を責めないでください。
ユルゲン

0

例外の目的は、ソフトウェアのフォールトトレランスを実現することです。ただし、関数によってスローされたすべての例外に応答する必要があると、抑制につながります。例外は、プログラマーにルーチンで特定の問題が発生する可能性があること、およびクライアントプログラマーがこれらの条件を認識し、必要に応じて対応する必要があることを認めさせる正式な構造にすぎません。

正直なところ、例外はプログラミング言語に追加された恨みであり、エラーケースの処理の責任を直接の開発者から将来の開発者に移す正式な要件を開発者に提供します。

優れたプログラミング言語は、C ++とJavaで例外がわかっているため、例外をサポートしていないと思います。関数からのあらゆる種類の戻り値に代替フローを提供できるプログラミング言語を選択する必要があります。プログラマーは、ルーチンのすべての形式の出力を予測し、自分のやり方があれば、それらを別のコードファイルで処理する責任があります。


0

次の場合に例外を使用します。

  • ローカルから回復できないエラーが発生しましたAND
  • エラーがプログラムから回復されない場合は終了する必要があります。

エラーを回復できる場合(ユーザーが数字の代わりに「apple」を入力した場合)、回復します(もう一度入力を求める、デフォルト値に変更するなど)。

ローカルからエラーを回復できないが、アプリケーションは続行できる場合(ユーザーがファイルを開こうとしたが、ファイルが存在しない場合)、エラーコードが適切です。

ローカルからエラーを回復できず、回復せずにアプリケーションを続行できない場合(メモリ/ディスク容量などが不足している場合)、例外が正しい方法です。


1
-1。質問を注意深く読んでください。ほとんどのプログラマーは、例外が特定のタイプのエラー処理に適切であるか、または決して適切ではないと考えています。例外を他のよりエキゾチックな形式のフロー制御に使用する可能性すら考慮していません。問題は、それはなぜですか?
j_random_hacker 2009年

また、注意深く読む必要があります。私は「それらがどのように使用されるかについて非常に保守的である背後にある哲学は何ですか?」と答えました。それらがどのように使用されるかについて保守的であることの背後にある私の哲学で。
ビル

私見あなたは保守主義が必要な理由を説明していません。なぜ彼らは時々「適切」なのですか?なぜいつもではないのですか?(ところで、あなたの提案したアプローチは問題ないと思います。多かれ少なかれ私が自分でやっていることです。質問の理由の多くは得られないと思います。)
j_random_hacker 2009年

OPは7つの異なる質問をしました。私は1つだけ答えることを選びました。反対票を投じる価値があると感じてすみません。
ビル

0

保守的に使用すべきだと誰が言ったのですか?フロー制御に例外を使用しないでください。それだけです。そして、それがすでに投げられたとき、誰が例外のコストを気にしますか?


0

私の2セント:

エラーが発生しないかのようにプログラムできるので、例外を使用するのが好きです。したがって、私のコードは読みやすく、あらゆる種類のエラー処理が散在しているわけではありません。もちろん、エラー処理(例外処理)は最後(キャッチブロック)に移動されるか、呼び出しレベルの責任と見なされます。

私にとっての良い例は、ファイル処理またはデータベース処理のいずれかです。すべてが正常であると想定し、最後に、または何らかの例外が発生した場合にファイルを閉じます。または、例外が発生したときにトランザクションをロールバックします。

例外の問題は、すぐに非常に冗長になることです。コードを非常に読みやすくし、通常の流れに焦点を当てることを目的としていましたが、一貫して使用する場合、ほとんどすべての関数呼び出しをtry / catchブロックでラップする必要があり、目的を達成できなくなります。

前述のParseIntの場合、例外のアイデアが好きです。値を返すだけです。パラメータが解析可能でない場合は、例外をスローします。それは一方であなたのコードをよりきれいにします。呼び出しレベルでは、次のようなことを行う必要があります

try 
{
   b = ParseInt(some_read_string);
} 
catch (ParseIntException &e)
{
   // use some default value instead
   b = 0;
}

コードはクリーンです。このようにParseIntが散らばっている場合は、例外を処理してデフォルト値を返すラッパー関数を作成します。例えば

int ParseIntWithDefault(String stringToConvert, int default_value=0)
{
   int result = default_value;
   try
   {
     result = ParseInt(stringToConvert);
   }
   catch (ParseIntException &e) {}

   return result;
}

結論として、私が議論を通して見逃したのは、エラー条件をもっと無視できるので、例外によってコードをより簡単に読みやすくすることができるという事実でした。問題:

  • 例外はまだどこかで処理する必要があります。追加の問題:c ++には、関数がスローする可能性のある例外を指定できる構文がありません(Javaのように)。したがって、呼び出しレベルは、どの例外を処理する必要があるかを認識していません。
  • すべての関数をtry / catchブロックでラップする必要がある場合、コードが非常に冗長になることがあります。しかし、これでも意味がある場合もあります。

そのため、バランスが取れない場合があります。


-1

申し訳ありませんが、答えは「理由から例外と呼ばれています」です。その説明は「経験則」です。ある問題ドメインの致命的な例外(英語の定義)は別の問題ドメインの通常の操作手順であるため、例外を使用する必要がある、または使用しない状況の完全なセットを提供することはできません。経験則は、盲目的に従うようには設計されていません。代わりに、ソリューションの調査をガイドするように設計されています。「これらは理由で例外と呼ばれます」は、呼び出し元が処理できる通常のエラーと、呼び出し元が特別なコーディング(キャッチブロック)なしでは処理できない異常な状況を事前に判断する必要があることを示しています。

プログラミングのほぼすべてのルールは、「本当に正当な理由がない限り、これを行わないでください」というガイドラインです。「gotoを使用しない」、「グローバル変数を避ける」、「正規表現は、問題の数を1つ増やす前に」 "など。例外も例外ではありません。。。。


...そして、質問者はそれが経験則であると(もう一度)聞くのではなく、なぜそれが経験則であるのを知りたいと思っています。-1。
j_random_hacker 2009年

私は私の返答でそれを認めました。明確な理由はありません。経験則は、定義上あいまいです。明確な理由がある場合、それは経験則であり、経験則ではありません。上記の他のすべての回答のすべての説明には警告が含まれているため、理由も説明されていません。
jmucchiello 2009年

明確な「理由」はないかもしれませんが、他の人が言及する部分的な「理由」があります。たとえば、「他の人がしていることだから」(IMHOは悲しいが本当の理由)や「パフォーマンス」(IMHOこの理由は通常誇張されている) 、しかしそれでも理由です)。同じことが、gotoの回避(通常、同等のループよりも制御フロー分析を複雑にする)やグローバル変数の回避(多くの結合を導入し、後のコード変更を困難にする可能性があり、通常は同じ)などの他の経験則にも当てはまります。目標は、他の方法との結合を少なくして達成できます)。
j_random_hacker 2009年

そして、これらすべての理由には、私の答えである警告の長いリストがあります。プログラミングの幅広い経験を超えた理由はありません。理由を超えた経験則がいくつかあります。同じ経験則により、「専門家」がその理由について意見を異にする可能性があります。あなた自身が一粒の塩で「パフォーマンス」を取ります。それが私のリストのトップになります。(「gotoなし」のように)フロー制御の問題が誇張されているので、フロー制御分析は私に登録されません。私の答えは、あなたが「trite」の答えをどのように使用するかを説明しようとしていることも指摘しておきます。
jmucchiello 2009年

すべての経験則に警告の長いリストがある限り、私はあなたに同意します。私が同意しないのは、ルールの元の理由と特定の警告を特定することは価値があると思うということです。(つまり、これらのことを熟考する時間が無限にあり、もちろん期限がない理想的な世界では;))それがOPが求めていたものだと思います。
j_random_hacker 2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.