エラー処理の考慮事項


31

問題:

長い間、私はexceptionsメカニズムが本当に何をすべきかを実際には解決しないと感じているため、メカニズムについて心配しています。

クレーム:このトピックについては長い議論があり、それらのほとんどはexceptionsエラーコードの比較と返送に苦労しています。これは間違いなくここのトピックではありません。

エラーを定義しようとすると、Bjarne Stroustrup&Herb SutterのCppCoreGuidelinesに同意します

エラーは、関数が公示された目的を達成できないことを意味します

クレーム:exceptionメカニズムは、エラーを処理するための言語セマンティックです。

クレーム:私には、タスクを達成しないための機能には「言い訳」がありません:機能が結果を保証できないように事前/事後条件を誤って定義したか、開発に時間を費やすために特定の例外的なケースが十分に重要ではないと考えられます解決策。IMOでは、通常のコードとエラーコードの処理の違いは(実装前に)非常に主観的なものだと考えています。

クレーム:例外を使用して、事前または事後条件が維持されていないことを示すことはexception、主にデバッグの目的で、メカニズムの別の目的です。私はexceptionsここのこの使用法をターゲットにしません。

多くの書籍、チュートリアル、およびその他のソースでは、エラー処理は非常に客観的な科学として示される傾向がexceptionsありますが、それは解決され、catchあらゆる状況から回復できる堅牢なソフトウェアが必要です。しかし、開発者としての数年間は、別のアプローチから問題を見るようになりました。

  • プログラマは、特定のケースがあまりにもまれに慎重に実装できないと思われる場合に例外をスローすることにより、タスクを単純化する傾向があります。この典型的なケースは次のとおりです。メモリ不足の問題、ディスクの空き容量の問題、破損したファイルの問題など。これで十分かもしれませんが、必ずしもアーキテクチャレベルから決定されるわけではありません。
  • プログラマは、ライブラリの例外に関するドキュメントを注意深く読んでいない傾向があり、通常、関数がスローするタイミングとタイミングを認識していません。さらに、たとえ知っていても、実際には管理していません。
  • プログラマーは、十分早くに例外をキャッチしない傾向があります。そして、それらをキャッチする場合、ほとんどの場合、ログに記録してさらにスローします。(最初のポイントを参照)。

これには2つの結果があります。

  1. 頻繁に発生するエラーは、開発の初期段階で検出され、デバッグされます(これは良いことです)。
  2. まれな例外は管理されず、ユーザーのホームでシステムがクラッシュします(素敵なログメッセージが表示されます)。エラーが報告される場合もあれば、そうでない場合もあります。

それを考慮すると、IMOのエラーメカニズムの主な目的は次のとおりです。

  1. 特定のケースが管理されていないコードで可視化する。
  2. この状況が発生した場合、問題のランタイムを関連コード(少なくとも呼び出し元)に伝えます。
  3. 回復メカニズムを提供します

exceptionエラー処理メカニズムとしてのセマンティックの主な欠陥はIMOです。throwソースコードのどこにa があるかは簡単にわかりますが、宣言を見ることで特定の関数がスローできるかどうかはわかりません。これは、上で紹介したすべての問題をもたらします。

言語は、言語の他の側面(たとえば、強力なタイプの変数)の場合ほど厳密にエラーコードを適用およびチェックしません。

解決策を試す

これを改善するために、非常に単純なエラー処理システムを開発しました。これは、通常のコードと同じレベルのエラー処理をエラー処理にしようと試みます。

アイデアは次のとおりです。

  • 各(関連する)関数は、success非常に軽いオブジェクトへの参照を受け取り、場合によってはエラー状態に設定することがあります。オブジェクトは、テキスト付きのエラーが保存されるまで非常に軽いです。
  • 提供されたオブジェクトに既にエラーが含まれている場合、関数はそのタスクをスキップすることが推奨されます。
  • エラーを無効にしないでください。

完全な設計では、明らかに各側面(約10ページ)を徹底的に検討し、OOPへの適用方法も検討します。

Successクラスの例:

class Success
{
public:
    enum SuccessStatus
    {
        ok = 0,             // All is fine
        error = 1,          // Any error has been reached
        uninitialized = 2,  // Initialization is required
        finished = 3,       // This object already performed its task and is not useful anymore
        unimplemented = 4,  // This feature is not implemented already
    };

    Success(){}
    Success( const Success& v);
    virtual ~Success() = default;
    virtual Success& operator= (const Success& v);

    // Comparators
    virtual bool operator==( const Success& s)const { return (this->status==s.status && this->stateStr==s.stateStr);}
    virtual bool operator!=( const Success& s)const { return (this->status!=s.status || this->stateStr==s.stateStr);}

    // Retrieve if the status is not "ok"
    virtual bool operator!() const { return status!=ok;}

    // Retrieve if the status is "ok"
    operator bool() const { return status==ok;}

    // Set a new status
    virtual Success& set( SuccessStatus status, std::string msg="");
    virtual void reset();

    virtual std::string toString() const{ return stateStr;}
    virtual SuccessStatus getStatus() const { return status; }
    virtual operator SuccessStatus() const { return status; }

private:
    std::string stateStr;
    SuccessStatus status = Success::ok;
};

使用法:

double mySqrt( Success& s, double v)
{
    double result = 0.0;
    if (!s) ; // do nothing
    else if (v<0.0) s.set(Error, "Square root require non-negative input.");
    else result = std::sqrt(v);
    return result;
}

Success s;
mySqrt(s, 144.0);
otherStuff(s);
saveStuff(s);
if (s) /*All is good*/;
else cout << s << endl;

私は自分の(自分の)コードの多くでそれを使用し、プログラマー(私)に例外的な可能性のあるケースとその解決方法(良い)についてさらに考えるように強制しています。ただし、学習曲線があり、現在使用しているコードとはうまく統合できません。

質問

プロジェクトでこのようなパラダイムを使用することの意味をよりよく理解したいと思います。

  • 問題の前提は正しいですか?または関連する何かを見逃しましたか?
  • ソリューションは優れたアーキテクチャのアイデアですか?または価格が高すぎますか?

編集:

メソッド間の比較:

//Exceptions:

    // Incorrect
    File f = open("text.txt"); // Could throw but nothing tell it! Will crash
    save(f);

    // Correct
    File f;
    try
    {
        f = open("text.txt");
        save(f);
    }
    catch( ... )
    {
        // do something 
    }

//Error code (mixed):

    // Incorrect
    File f = open("text.txt"); //Nothing tell you it may fail! Will crash
    save(f);

    // Correct
    File f = open("text.txt");
    if (f) save(f);

//Error code (pure);

    // Incorrect
    File f;
    open(f, "text.txt"); //Easy to forget the return value! will crash
    save(f);

    //Correct
    File f;
    Error er = open(f, "text.txt");
    if (!er) save(f);

//Success mechanism:

    Success s;
    File f;
    open(s, "text.txt");
    save(s, f); //s cannot be avoided, will never crash.
    if (s) ... //optional. If you created s, you probably don't forget it.

25
「この質問は研究の努力を示しています。それは有用で明確です」と賛成したのは、同意するからではなく、かなりの考えが見当違いだと思います。(詳細は回答の後に続く場合があります。)
マーティンBa

2
絶対に、私はそれを理解し、同意します!この質問の目的は批判を受けることです。また、OPが正しいことではなく、良い/悪い質問を示す質問のスコア。
エイドリアンメアリー

2
私が正しく理解している場合、例外についてのあなたの主な不満は、人々がそれらを処理する代わりに(c ++で)無視できることです。ただし、Success構造には、設計上同じ欠陥があります。例外のように、彼らはそれを無視します。さらに悪いことに、それはより冗長で、カスケードリターンにつながり、上流で「キャッチ」することさえできません。
-dagnelies

3
モナドのようなものを使用しないのはなぜですか?エラーは暗黙のうちに発生しますが、実行中は沈黙しません。実際、コードを見たときに最初に思ったのは「モナド、いい」です。それらを見てください。
bash0r

2
例外が好きな主な理由は、特定のコードブロックから予期しないエラーをすべてキャッチし、一貫して処理できることです。はい、コードがそのタスクを実行すべきでない正当な理由はありません-「バグがありました」は悪い理由ですが、それでも起こります。そして、それが起こったら、原因を記録してメッセージを表示するか、再試行します (リモートシステムとの複雑で再起動可能なやり取りを行うコードがあります。リモートシステムがダウンした場合、ログに記録して最初から再試行します)
user253751

回答:


32

エラー処理は、おそらくプログラムの最も難しい部分です。

一般に、エラー状態があることを認識するのは簡単です。ただし、回避できない方法で信号を送り、適切に処理することは非常に困難です(アブラハムの例外安全レベルを参照)。

Cでは、エラーを通知するのは、ソリューションと同型のリターンコードによって行われます。

C ++のための例外を導入短来るこのようなアプローチの; つまり、呼び出し元がエラーが発生したかどうかを確認することを忘れない場合にのみ機能し、そうでなければひどく失敗します。「いつでも...」と自分自身が言っていることに気付くたびに、問題が発生します。人間は気にかけてもそれほど細心ではありません。

ただし、問題は例外に独自の問題があることです。つまり、非表示/非表示の制御フロー。これは、コードのロジックがエラー処理ボイラープレートによって難読化されないように、エラーケースを隠すことを目的としています。エラーパスを不可解にすることを犠牲にして、「ハッピーパス」をより明確に(そして高速に!)します。


他の言語がどのようにこの問題に取り組んでいるかを見るのは面白いと思います。

  • Javaは例外をチェックしました(未チェックの例外もあります)。
  • Goはエラーコード/パニックを使用します。
  • Rustは合計タイプ /パニックを使用します)。
  • FP言語全般。

以前はC ++に何らかの形式のチェック例外がありましたが、noexcept(<bool>)代わりに非推奨になり、基本に向かって単純化されていることに気付いたかもしれません:関数がスローされる可能性があると宣言されているか、そうでないと宣言されています。チェック例外は、拡張性に欠けるという点でやや問題があり、厄介なマッピング/ネストを引き起こす可能性があります。複雑な例外階層(仮想継承の主な使用例の1つは例外です...)。

対照的に、GoとRustは次のアプローチを取ります。

  • エラーは帯域内で通知する必要があり、
  • 例外は、本当に例外的な状況に使用する必要があります。

後者は、(1)例外にパニックという名前を付け、(2)ここに型階層/複雑な句がないという点でかなり明白です。この言語は、「パニック」のコンテンツを検査する機能を提供しません。型階層もユーザー定義コンテンツも、「おっと、物事がうまくいかなかったので回復できません」というだけです。

これにより、ユーザーは適切なエラー処理を効果的に使用できますが、例外的な状況(「待ってください、まだ実装していません!」など)でも簡単に解決できます。

もちろん、残念ながら、Goのアプローチは、エラーをチェックするのを簡単に忘れることができるという点で、あなたのものとよく似ています...

...ただし、Rustのアプローチは、主に2つのタイプに集中しています。

  • Optionに類似していますstd::optional
  • Result、OkとErrの2つの可能性があります。

成功を確認せずに誤って結果を使用する機会がないため、これは非常にすてきです。そうすると、プログラムがパニックになります。


FP言語は、3つの層に分割できる構造でエラー処理を形成します。-Functor-Applicative / Alternative-Monads / Alternative

HaskellのFunctorタイプクラスを見てみましょう。

class Functor m where
  fmap :: (a -> b) -> m a -> m b

まず第一に、タイプクラスはインターフェイスに多少似ていますが、同等ではありません。Haskellの関数シグネチャは、最初は少し怖いように見えます。しかし、それらを解読しましょう。この関数fmapは、に多少似た関数を最初のパラメーターとして受け取りますstd::function<a,b>。次はm a。あなたは想像できるmようなものとstd::vectorし、m aのようなものなどstd::vector<a>。しかし、違いは、m a明示的にする必要があるということではありませんstd:vector。だから、それも可能性がありstd::optionます。またはなどのFunctor特定の型の型クラスのインスタンスがあることを言語に伝えることで、その型の関数を使用できます。同じことは、型クラスのために行われなければならない、とstd::vectorstd::optionfmapApplicativeAlternativeMonadこれにより、ステートフルで失敗する可能性のある計算を実行できます。Alternative型クラスは、エラー回復の抽象化を実装しています。それによって、a <|> b用語aまたは用語のいずれかを意味するようなものを言うことができますb。どちらの計算も成功しない場合、それはまだエラーです。

HaskellのMaybeタイプを見てみましょう。

data Maybe a
  = Nothing
  | Just a

これは、を期待する場所でMaybe aNothingまたはのいずれかを取得することを意味しますJust a。見るとfmap、上から、実装は次のようになり

fmap f m = case m of
  Nothing -> Nothing
  Just a -> Just (f a)

このcase ... of表現はパターンマッチングと呼ばれ、OOPの世界ではとして知られているものに似ていvisitor patternます。ラインを想像しcase m ofm.apply(...)ドットをディスパッチ機能を実装するクラスのインスタンス化したものです。case ... of式の下の行は、クラスのフィールドを名前でスコープ内に直接持ってくるそれぞれのディスパッチ関数です。ではNothing、私たちが作成したブランチNothingとにJust a支店我々は唯一の値の名前aと別のものを作成するJust ...変換関数では、fに適用されますa。次のように読んでくださいnew Just(f(a))

これにより、実際のエラーチェックを抽象化しながら、誤った計算を処理できるようになりました。この種の計算を非常に強力にする他のインターフェイスの実装が存在します。実際、MaybeRustのOption-Typeのインスピレーションです。


代わりにSuccessクラスを作り直すことをお勧めしますResult。アレクサンドレスクは実際に、と呼ばれる本当に近いものをexpected<T>提案しました

Rustの命名とAPIに固執するのは、それが文書化されて機能しているからです。もちろん、Rustには?、コードをより甘くする気の利いた接尾辞演算子があります。C ++では、TRYマクロとGCCのステートメント式を使用してエミュレートします。

template <typename E>
struct Error {
    Error(E e): error(std::move(e)) {}

    E error;
};

template <typename E>
Error<E> error(E e) { return Error<E>(std::move(e)); }

template <typename T, typename E>
struct [[nodiscard]] Result {
    template <typename U>
    Result(U u): ok(true), data(std::move(u)), error() {}

    template <typename F>
    Result(Error<F> f): ok(false), data(), error(std::move(f.error)) {}

    template <typename U, typename F>
    Result(Result<U, F> other):
        ok(other.ok), data(std::move(other.data)),  error(std::move(other.error)) {}

    bool ok = false;
    T data;
    E error;
};

#define TRY(Expr_) \
    ({ auto result = (Expr_); \
       if (!result.ok) { return result; } \
       std::move(result.data); })

注:これResultはプレースホルダーです。適切な実装では、カプセル化とを使用しますunion。しかし、ポイントを理解するには十分です。

これは私が書くことを可能にします(実際にそれを見る):

Result<double, std::string> sqrt(double x) {
    if (x < 0) {
        return error("sqrt does not accept negative numbers");
    }
    return x;
}

Result<double, std::string> double_sqrt(double x) {
    auto y = TRY(sqrt(x));
    return sqrt(y);
}

私は本当にきれいだと思う:

  • エラーコード(またはSuccessクラス)の使用とは異なり、エラーのチェックを忘れると、ランダムな動作ではなくランタイムエラー1が発生します。
  • 例外の使用とは異なり、呼び出しサイトでどの関数が失敗する可能性があるかは明らかなので、驚くことはありません。
  • C ++-2X標準ではconcepts、標準に到達する可能性があります。これにより、エラーの種類よりも選択肢を残すことができるため、この種のプログラミングがはるかに楽しくなります。たとえば、std::vectorその結果の実装により、すべての可能なソリューションを一度に計算できます。または、あなたが提案したように、エラー処理を改善することもできます。

1 適切にカプセル化Resultされた実装で;)


注:例外とは異なり、このライトウェイトにResultはバックトレースがないため、ロギングの効率が低下します。少なくともエラーメッセージが生成されたファイル/行番号をログに記録し、通常は豊富なエラーメッセージを記述すると便利です。これは、TRYマクロを使用するたびにファイル/行をキャプチャし、基本的にバックトレースを手動で作成するか、コールスタックlibbacktrace内のシンボルをリストするなどのプラットフォーム固有のコードとライブラリを使用することでさらに複雑になります。


ただし、大きな警告が1つあります。既存のC ++ライブラリ、さらにstdは例外に基づいています。サードパーティのライブラリのAPIはアダプタでラップする必要があるため、このスタイルを使用するのは困難な戦いになります...


3
そのマクロは...非常に間違っています。私({...})はいくつかのgcc拡張であると仮定しますが、そうであってもそうではないif (!result.ok) return result;でしょうか?あなたの状態は逆に表示され、エラーの不必要なコピーを作成します。
Mooingダック

@MooingDuck答えは、それ({...})がgccのステートメント式であると説明しています
ジェームズリン


1
C ++ 17を使用しstd::variantResultいる場合は、を使用して実装することをお勧めします。また、エラーを無視した場合に警告を表示するには、次を使用します[[nodiscard]]
ジャスティン

2
@Justin:std::variant例外処理に関するトレードオフを考えると、使用するかどうかは好みの問題です。[[nodiscard]]本当に純粋な勝利です。
マチューM.17年

46

クレーム:例外メカニズムはエラーを処理するための言語セマンティックです

例外は制御フローのメカニズムです。この制御フローメカニズムの動機は、エラー処理が非常に反復的であり、ロジックの主要部分とほとんど関係がないという一般的なケースで、エラー処理を非エラー処理コードから明確に分離することでした。

クレーム:私には、タスクを達成しないための機能には「言い訳」がありません:機能が結果を保証できないように事前/事後条件を間違って定義したか、開発に時間を費やすのに十分な特定の例外的なケースが十分に考慮されていません解決策

考慮:ファイルを作成しようとしています。ストレージデバイスがいっぱいです。

さて、これは私の前提条件を定義するのに失敗したわけではありません。共有ストレージはこれを満たせない競合状態の影響を受けるため、「十分なストレージが必要」を一般に前提条件として使用できません。

だから、私のプログラムは何らかの形でスペースを解放してから正常に続行する必要があります。そうでなければ、私は「ソリューションを開発する」のが面倒です。これは率直に言って無意味なようです。共有ストレージを管理するための「ソリューション」は私のプログラムの範囲外であり、プログラムが正常に失敗し、ユーザーがスペースを解放するか、またはストレージを追加すると、再実行できるようになります罰金


成功クラスが行うことは、プログラムロジックとの明示的なエラー処理のインターリーブです。すべての関数は、実行する前に、何らかのエラーがすでに発生しているかどうかを確認する必要があります。つまり、何も実行すべきではありません。すべてのライブラリー関数は、もう1つ引数を付けて別の関数にラップする必要があり(そして、うまくいけば完璧な転送)、まったく同じことを行います。

また、mySqrt関数は、失敗した(または前の関数が失敗した)場合でも値を返す必要があることに注意してください。したがって、魔法の値(などNaN)を返すか、プログラムに不定の値を注入して、何も使用しないことを望みます。

正確性とパフォーマンスのために、進行ができなくなったら制御をスコープ外に戻すことをお勧めします。これは、例外と早期復帰を伴う Cスタイルの明示的なエラーチェックの両方で実現されます。


比較のために、実際に機能するアイデアの例は 、Haskellのエラーモナドです。システムに対する利点は、ロジックの大部分を通常通りに記述してから、1つのステップが失敗したときに評価を停止するモナドにラップすることです。このように、エラー処理システムに直接触れるコードは、失敗する可能性がある(エラーをスローする)コードと、障害に対処する必要がある(例外をキャッチする)コードのみです。

しかし、モナドスタイルと遅延評価がC ++にうまく変換されるかどうかはわかりません。


1
あなたの答えのおかげで、トピックに光を追加します。私は、ユーザーがに反対するだろうと思いand allowing my program to fail gracefully, and be re-run、彼はちょうど2時間の仕事を失ったとき:
エイドリアンMaire

14
解決策は、ファイルを作成する可能性のあるすべての場所で、状況を修正して再試行するようユーザーに求める必要があることを意味します。それから他の問題が発生する可能性があるため、何らかの方法でローカルに修正する必要があります。例外を除いて、std::exception論理操作の上位レベルでキャッチし、ユーザーに"ex.what()が原因でXが失敗しました"と伝え、準備ができたときに操作全体を再試行することを提案します。
役に立たない

13
@AdrianMaire:「正常に失敗して再実行できるようにする」ことは、として実装することもできshowing the Save dialog again along with an error message and allowing the user to specify an alternative location to tryます。これは、通常、最初の保管場所がいっぱいであることを検出するコードからは実行できない問題の適切な処理です。
バートヴァンインゲンシェナウ

3
@Useless Lazy評価は、エラーモナドの使用とは関係ありません。これは、Rust、OCaml、F#などの厳格な評価言語がすべてそれを多用していることからも明らかです。
8ビットツリー

1
高品質のソフトウェアのためのIMO @Useless、それがないことを理にかなっ「すべての場所にファイルを作成することがありますが、あなたは状況や再試行を修正を促す必要があります」。初期のプログラマーは多くの場合、エラー回復に向けてかなりの時間を費やしました。少なくとも、KnuthのTeXプログラムにはエラーがいっぱいです。そして、彼の「リテラシープログラミング」フレームワークを使用して、エラー処理を別のセクションで保持する方法を見つけました。そのため、コードは読みやすく、エラー回復はより慎重に記述されます(エラー回復セクションを記述するときは、それがポイントであり、プログラマーはより良い仕事をする傾向があります)。
シュ

15

プロジェクトでこのようなパラダイムを使用することの意味をよりよく理解したいと思います。

  • 問題の前提は正しいですか?または関連する何かを見逃しましたか?
  • ソリューションは優れたアーキテクチャのアイデアですか?または価格が高すぎますか?

あなたのアプローチはソースコードにいくつかの大きな問題をもたらします:

  • 値をチェックすることを常に覚えているクライアントコードに依存します s。これは、エラー処理アプローチのリターンコードを使用する場合と、言語に例外が導入された理由の1つに共通しています。例外がある場合、失敗しても黙って失敗することはありません。

  • このアプローチを使用してコードを記述するほど、エラー処理のために追加する必要のあるエラーボイラープレートコードも増え(コードは最小限になりません)、メンテナンス作業が増えます。

しかし、開発者としての数年間は、別のアプローチから問題を見るようになりました。

これらの問題の解決策は、テクニカルリードレベルまたはチームレベルでアプローチする必要があります。

プログラマは、特定のケースがあまりにもまれに慎重に実装できないと思われる場合に例外をスローすることにより、タスクを単純化する傾向があります。この典型的なケースは次のとおりです。メモリ不足の問題、ディスクの空き容量の問題、破損したファイルの問題など。これで十分かもしれませんが、必ずしもアーキテクチャレベルから決定されるわけではありません。

スローされる可能性のあるすべての種類の例外を常に処理していることに気付いた場合、設計は良くありません。どのエラーが処理されるかは、開発者が実装する気分ではなく、プロジェクトの仕様に従って決定する必要があります。

自動テストを設定し、ユニットテストの仕様と実装を分離することで対処します(2人の異なる人にこれを行わせます)。

プログラマーは慎重にドキュメントを読んでいない傾向があります[...]さらに、知っていても実際には管理していません。

これ以上のコードを記述して対処することはありません。最善の策は、細心の注意を払って適用されたコードレビューです。

プログラマーは、十分早くに例外をキャッチしない傾向があります。そして、それらをキャッチする場合、ほとんどの場合、ログに記録してさらにスローします。(最初のポイントを参照)。

適切なエラー処理は困難ですが、戻り値の場合よりも例外のほうが面倒ではありません(実際に返されるか、入出力引数として渡されるかは関係ありません)。

エラー処理の最も難しい部分は、エラーを受け取る方法ではなく、エラーが発生した場合にアプリケーションが一貫した状態を維持する方法です。

これに対処するには、エラー状態の特定と実行(より多くのテスト、より多くのユニット/統合テストなど)により注意を払う必要があります。


12
引数としてインスタンスを受け取るたびにチェックすることを忘れない場合、エラー後のすべてのコードはスキップされます。これは、「このアプローチで作成するコードが多いほど、追加する必要があるエラーボイラープレートコードも多くなる」ことを意味します。成功インスタンスのifsでコードをなぞる必要があります。忘れるたびにバグになります。チェックを忘れることによって引き起こされる2番目の問題:再度チェックするまで実行されるコードは、まったく実行されるべきではありません(チェックを忘れるとデータが破損します)。
utnapistim

11
いいえ、例外を処理する(またはエラーコードを返す)ことはクラッシュではありません-エラー/例外が論理的に致命的であるか、または処理しないことを選択しない限り。エラーが以前に発生したかどうかをすべてのステップで明示的に確認することなく、エラーのケースを処理する機会がまだあります
役に立たない

11
@AdrianMaire私が取り組んでいるほとんどすべてのアプリケーションでは、静かに継続するよりもクラッシュすることを非常に好みます。私は、ビジネスに不可欠なソフトウェアに取り組んでいます。そこでは、悪い出力を取り、それを操作し続けると、多くのお金が失われる可能性があります。正確性が重要であり、クラッシュが許容できる場合、ここで例外に非常に大きな利点があります。
クリスヘイズ

1
@AdrianMaire-ifステートメントを忘れるという方法で例外を処理することを忘れるのははるかに難しいと思います...例外-例外の主な利点は、どの層がそれらを処理するかです。システム例外をさらにバブルアップして、アプリケーションレベルのエラーメッセージを表示したいが、低レベルで知っている状況を処理したい場合があります。サードパーティのライブラリまたは他の開発者コードを使用している場合、これが本当に唯一の選択肢です
...-ミルニー

5
@Adrian間違いなく、あなたは私が書いたものを読み間違えたか、後半を逃したようです。私の全体的なポイントは、テスト/開発中に例外がスローされ、開発者がそれらを処理する必要があることに気付くことです。重要なのは、本番環境での完全に未処理の例外の結果は、未チェックのエラーコードの結果よりも望ましいということです。エラーコードを見逃した場合、間違った結果を取得して使用し続けます。例外を見逃すと、アプリケーションがクラッシュし、実行を継続しません。間違った結果ではなく、結果が得られません。(続き)
ミンダミドル氏
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.