web-dev-qa-db-ja.com

例外の契約を作成して適用するにはどうすればよいですか?

私はチームリードに、bool isSuccessfulまたはエラーコードを含む列挙型を返すのではなく、C++で例外を使用できるように説得しようとしています。しかし、私は彼のこの批判に対抗することはできません。

このライブラリを考えてみましょう:

_class OpenFileException() : public std::runtime_error {
}

void B();
void C();

/** Does blah and blah. */
void B() {
    // The developer of B() either forgot to handle C()'s exception or
    // chooses not to handle it and let it go up the stack.
    C();
};

/** Does blah blah.
 *
 * @raise OpenFileException When we failed to open the file. */
void C() {
    throw new OpenFileException();
};
_
  1. 開発者がB()関数を呼び出すことを検討してください。彼はそのドキュメントをチェックし、例外が返されないことを確認したため、何もキャッチしようとしません。このコードは、本番環境のプログラムをクラッシュさせる可能性があります。

  2. 開発者がC()関数を呼び出すことを検討してください。彼はドキュメントをチェックしないので、例外をキャッチしません。この呼び出しは安全ではなく、本番環境でプログラムをクラッシュさせる可能性があります。

しかし、この方法でエラーをチェックすると:

_void old_C(myenum &return_code);
_

その関数を使用する開発者は、引数を指定しないとコンパイラーから警告を受け、「ああ、これは私がチェックする必要のあるエラーコードを返す」と言うでしょう。

例外を安全に使用して、何らかの契約を結ぶにはどうすればよいですか?

33
DBedrenko

これは例外に対する正当な批判です。それらは、コードを返すなどの単純なエラー処理よりも多くの場合目に見えません。そして、「契約」を強制する簡単な方法はありません。ポイントの一部は、より高いレベルで例外をキャッチできるようにすることです(すべてのレベルですべての例外をキャッチする必要がある場合、とにかく、エラーコードを返すのとどのくらい違いますか?)。これは、コードが適切に処理されない他のコードによって呼び出される可能性があることを意味します。

例外には欠点もあります。費用対効果に基づいてケースを作成する必要があります。
私はこれらの2つの記事が役に立ったと感じました: 例外の必要性 および 例外のすべてが間違っています。 また、 このブログ投稿は多くの専門家の意見を提供します)例外については、C++に重点を置いて 。専門家の意見は例外を支持する傾向があるようですが、明確なコンセンサスにはほど遠いものです。

チームリードの説得力については、これは適切な戦いではないかもしれません。特にレガシーコードではありません。上記の2番目のリンクで述べたように:

例外は、例外に対して安全ではないコードを通じて伝播することはできません。したがって、例外の使用は、プロジェクト内のすべてのコードが例外に対して安全でなければならないことを意味します。

例外を使用するコードを、主に使用しないプロジェクトに追加することは、おそらく改善されないでしょう。他の点では適切に記述されたコードで例外を使用しないことは、致命的な問題には程遠いものです。用途や依頼する専門家によっては、まったく問題にならない場合もあります。あなたはあなたの戦いを選ぶ必要があります。

これはおそらく、少なくとも新しいプロジェクトが開始されるまでは、私が努力する議論ではありません。また、新しいプロジェクトがある場合でも、レガシーコードを使用するのでしょうか。

46
user82096

文字通り本全体が書かれているので、答えはせいぜい要約です。あなたの質問に基づいて、私が作成する価値があると思う重要なポイントのいくつかを以下に示します。完全なリストではありません。


例外はあちこちでキャッチされないように意図されています。

アプリケーションの種類(Webサーバー、ローカルサービス、コマンドラインユーティリティなど)に応じて、メインループに一般的な例外ハンドラーがある限り、通常、必要な例外ハンドラーはすべて揃っています。

私のコードでは、メインループの外側に、catchステートメントが少ししかありません。そして、それは現代のC++における一般的なアプローチのようです。


例外と戻りコードは相互に排他的ではありません。

これをオールオアナッシングアプローチにしないでください。例外は例外的な状況で使用する必要があります。 「Config file not found」、「Disk Full」など、ローカルで処理できないもの。

ユーザーが指定したファイル名が有効かどうかの確認などの一般的なエラーは、例外のユースケースではありません。これらの場合は、代わりに戻り値を使用してください。

上記の例からわかるように、「ファイルが見つかりません」は、ユースケースに応じて、例外または戻りコードのいずれかになる可能性があります。

したがって、絶対的なルールはありません。大まかなガイドラインは次のとおりです。ローカルで処理できる場合は、戻り値にします。ローカルで処理できない場合は、例外をスローします。


例外の静的チェックは役に立ちません。

例外はとにかくローカルで処理されないため、通常、どの例外をスローできるかは重要ではありません。唯一の有用な情報は、例外をスローできるかどうかです。

Javaには静的チェックがありますが、通常は失敗した実験と見なされ、ほとんどの言語(特にC#)にはそのタイプの静的チェックがないためです。 これは良い読みです C#にない理由について。

これらの理由により、C++はthrow(exceptionA, exceptionB)を優先してnoexcept(true)を廃止しました。デフォルトでは、関数がスローできるので、ドキュメントで明示的に他のことを約束しない限り、プログラマはそれを期待する必要があります。


例外セーフコードの記述は、例外ハンドラの記述とは関係ありません。

例外安全なコードを書くことは、例外ハンドラーを書かないようにする方法のすべてだと私はむしろ言いたいです!

ほとんどのベストプラクティスは、例外ハンドラーの数を減らすことを目的としています。コードを1回記述し、自動的に呼び出す-例RAII経由-どこにでも同じコードをコピーして貼り付けるよりもバグが少なくなります。

26
Sjoerd

C++プログラマーは例外仕様を探しません。彼らは例外の保証を探します。

コードの一部が例外をスローしたとします。プログラマはどのような仮定を立てることができますか?コードの記述方法から、例外の結果、コードは何を保証しますか?

または、特定のコードが決してスローしないことを保証できる(つまり、OSプロセスが終了すること以外に何もない)可能性はありますか?

「ロールバック」という言葉は、例外に関する議論で頻繁に発生します。 (明示的に文書化されている)有効な状態にロールバックできることは、例外保証の例です。例外の保証がない場合、プログラムはその場で終了するはずです。それ以降に実行されるコードが意図したとおりに機能することさえ保証されないためです。たとえば、メモリが破損している場合、それ以降の操作は技術的に未定義の動作です。

さまざまなC++プログラミング手法により、例外の保証が促進されます。 RAII(スコープベースのリソース管理)は、クリーンアップコードを実行し、通常の場合と例外的な場合の両方でリソースが確実に解放されるようにするメカニズムを提供します。オブジェクトに変更を加える前にデータのコピーを作成すると、操作が失敗した場合にそのオブジェクトの状態を復元できます。等々。

このStackOverflowの質問 への回答は、C++プログラマーがコードに発生する可能性のあるすべての可能な障害モードを理解し、障害にもかかわらずプログラム状態の有効性を保護しようとする長さを垣間見せます。 C++コードの行ごとの分析が習慣になります。

C++(プロダクション用)で開発する場合、詳細に目を通す余裕はありません。また、バイナリblob(非オープンソース)はC++プログラマの悩みの種です。バイナリblobを呼び出す必要があり、そのblobが失敗した場合、C++プログラマが次に行うのはリバースエンジニアリングです。

参照: http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety -例外の安全性を参照してください。

C++は、例外仕様の実装に失敗しました。後で他の言語で分析すると、例外の仕様は単に実用的ではないことがわかります。

それが失敗した試みである理由:厳密にそれを強制するためには、それは型システムの一部でなければなりません。しかし、そうではありません。コンパイラーは例外指定をチェックしません。

なぜC++がそれを選んだのか、そして他の言語(Java)の経験が例外仕様が根拠のないことを証明する理由:関数の実装を変更すると(たとえば、新しい種類の関数をスローする可能性がある別の関数を呼び出す必要がある)例外)、厳密な例外仕様の実施は、その仕様も更新する必要があることを意味します。これは伝播します-単純な変更のために、何十または何百もの関数の例外仕様を更新しなければならない場合があります。抽象基本クラス(C++のインターフェースに相当)では状況が悪化します。例外仕様がインターフェースに適用されている場合、インターフェースの実装は、異なるタイプの例外をスローする関数を呼び出すことができません。

参照: http://www.gotw.ca/publications/mill22.htm

C++ 17以降、[[nodiscard]]属性は、関数の戻り値で使用できます(参照: https://stackoverflow.com/questions/39327028/can-ac-function-be-declared-such-that-the-return-value-cannot -be-ignored )。

それで、コードを変更し、それが新しい種類の障害状態(つまり、新しいタイプの例外)をもたらす場合、それは重大な変更ですか?呼び出し元にコードの更新を強いるべきか、少なくとも変更について警告するべきか?

C++プログラマーが例外仕様の代わりに例外保証を探す引数を受け入れる場合、その答えは、新しい種類の障害条件が例外を壊さず、コードが以前に約束したことを保証しない場合、重大な変更ではないということです。

8
rwong

C()関数を呼び出す開発者を考えます。彼はドキュメントをチェックしないため、例外をキャッチしません。呼び出しは安全ではありません

完全に安全です。彼はすべての場所で例外をキャッチする必要はなく、実際に何か有用なことができる場所でtry/catchを投げることができます。スレッドからリークすることを彼が許可した場合のみ、それは安全ではありませんが、それが起こるのを防ぐことは通常難しくありません。

4
DeadMG

重要なシステムを構築している場合は、チームリーダーのアドバイスに従うことを検討し、例外は使用しないでください。これは ロッキードマーティンの合同ストライク戦闘機の航空機C++コーディング基準 のAVルール208です。一方、 [〜#〜] misra [〜#〜] MISRA準拠のソフトウェアシステムを構築している場合、C++ガイドラインには例外を使用できる場合と使用できない場合に関する非常に具体的なルールがあります。

重要なシステムを構築している場合は、静的分析ツールも実行している可能性があります。多くの静的分析ツールは、メソッドの戻り値をチェックしないと警告を発し、エラー処理の欠落のケースをすぐに明らかにします。私の知る限りでは、適切な例外処理を検出するための同様のツールサポートはそれほど強力ではありません。

最終的に、私は 契約による設計 および防御的プログラミングと静的分析を組み合わせることで、例外よりも重要なソフトウェアシステムの方が安全であると主張します。

3
Thomas Owens