6

重複の可能性:
防御的なプログラミング

私たちは今朝、防御的なプログラミングの主題について素晴らしい議論をしました。ポインターが渡され、それが有効かどうかがチェックされなかったコードレビューがありました。

一部の人々は、nullポインタのチェックだけが必要であると感じました。渡されるすべてのメソッドではなく、より高いレベルでチェックできるかどうか、そしてポイントの反対側のオブジェクトが特定の要件を満たしていない場合、nullのチェックは非常に限定されたチェックであるかどうかを疑問視しました。

nullをチェックする方が何もないよりはましだということを理解し、同意しますが、nullだけをチェックすると、範囲が限定されるため、誤った安心感が得られると思います。ポインタが使用可能であることを確認したい場合は、null以外のものをチェックしてください。

このテーマについてのあなたの経験は何ですか?従属メソッドに渡されるパラメーターの防御をコードにどのように記述しますか?

4

14 に答える 14

17

コードコンプリート2のエラー処理の章で、バリケードの概念を紹介しました。本質的に、バリケードは、そこに入るすべての入力を厳密に検証するコードです。バリケード内のコードは、無効な入力がすでに処理されており、受信された入力が良好であると想定できます。バリケード内では、コードはバリケード内の他のコードから渡された無効なデータについてのみ心配する必要があります。条件を主張し、賢明な単体テストを行うことで、バリケードされたコードに対する信頼を高めることができます。このようにして、バリケードでは非常に防御的にプログラムしますが、バリケード内ではそれほど防御的ではありません。別の見方をすれば、バリケードでは常にエラーを正しく処理し、バリケード内ではデバッグビルドで条件をアサートするだけです。

生のポインタを使用する限り、通常、できる最善の方法は、ポインタがnullではないことを表明することです。そのメモリに何が含まれているのかがわかっている場合は、内容が何らかの方法で一貫していることを確認できます。これは、なぜそのメモリが、その整合性自体を検証できるオブジェクトにラップされていないのかという疑問を投げかけます。

では、なぜこの場合に生のポインターを使用しているのですか?参照またはスマートポインタを使用する方が良いでしょうか?ポインターには数値データが含まれていますか?含まれている場合は、そのポインターのライフサイクルを管理するオブジェクトにラップする方がよいでしょうか?

これらの質問に答えることで、防御しやすい設計になってしまうという点で、より防御的な方法を見つけることができます。

于 2009-12-16T18:36:02.960 に答える
13

防御するための最善の方法は、実行時にnullのポインターをチェックするのではなく、最初からnullである可能性のあるポインターの使用を避けることです。

渡されるオブジェクトがnullであってはならない場合は、参照を使用してください。または値で渡します!または、ある種のスマートポインタを使用します。

防御的なプログラミングを行う最良の方法は、コンパイル時にエラーをキャッチすることです。オブジェクトがnullであるか、ガベージを指していることがエラーであると見なされる場合は、それらをコンパイルエラーにする必要があります。

最終的に、ポインタが有効なオブジェクトを指しているかどうかを知る方法はありません。したがって、1つの特定のコーナーケース(実際に危険なケースよりもはるかに一般的ではない、無効なオブジェクトを指すポインター)をチェックするのではなく、有効性を保証するデータ型を使用してエラーを不可能にします。

コンパイル時にC++のように多くのエラーをキャッチできる別の主流言語は考えられません。その機能を使用します。

于 2009-12-16T18:44:15.883 に答える
4

ポインタが有効かどうかを確認する方法はありません。

于 2009-12-16T18:40:12.000 に答える
3

私は少し極端かもしれませんが、防御プログラミングは好きではありません。原則を導入したのは怠惰だと思います。

この特定の例では、ポインタがnullではないことを表明する意味はありません。nullポインターが必要な場合は、代わりに参照を使用するよりも、実際にそれを強制する(そして同時に明確に文書化する)より良い方法はありません。そして、それは実際にコンパイラによって強制され、実行時にジルチを要しないドキュメントです!!

一般的に、私は「raw」タイプを直接使用しない傾向があります。説明しましょう:

void myFunction(std::string const& foo, std::string const& bar);

fooとの可能な値は何barですか?まあ、それはaが含むかもしれないものによってのみかなり制限されていstd::stringます...それはかなり曖昧です。

一方で:

void myFunction(Foo const& foo, Bar const& bar);

はるかに良いです!

  • 引数の順序を誤って逆にした場合、コンパイラによって検出されます
  • 各クラスは、値が正しいことを確認する責任があり、ユーザーに負担はかかりません。

私は強いタイピングを好む傾向があります。アルファベット文字のみで構成され、最大12文字のエントリがある場合は、内部で割り当てを確認するために使用されるstd::string単純なメソッドを使用して、をラップする小さなクラスを作成し、代わりにそのクラスを渡します。validateこのようにして、検証ルーチンを1回テストする場合、その値が到達する可能性のあるすべてのパスについて実際に心配する必要はありません。>値が到達したときに検証されます。

もちろん、それはコードがテストされるべきではないということではありません。私が強力なカプセル化を好むというだけであり、入力の検証は私の意見では知識のカプセル化の一部です。

そして、例外なくルールはあり得ないので...公開されたインターフェースは、何が起こるかわからないため、検証コードで肥大化する必要があります。ただし、BOM内の自己検証オブジェクトを使用すると、一般的に非常に透過的です。

于 2009-12-16T20:01:19.293 に答える
3

深刻なことに、それはあなたがあなたに負わせなければならないバグの数に依存します。

ヌルポインタをチェックすることは間違いなく私が必要と考えるものですが、十分ではありません。コードのエントリポイント(たとえば、入力検証=そのポインタは何か有用なものを指しているか)と出口ポイント(たとえば、ポインタが何か有用なものを指していると思ったが、たまたまそれが原因である)から始めて使用できる確かな原則は他にもたくさんあります。例外をスローするコード)。

要するに、あなたのコードを呼び出すすべての人があなたの人生を台無しにするために最善を尽くそうとしていると仮定すると、おそらく最悪の犯人がたくさん見つかるでしょう。

明確にするために編集:他のいくつかの答えはユニットテストについて話している。テストコードは、テストしているコードよりも価値がある場合があると確信しています(値を測定している人によって異なります)。とはいえ、ユニットテストも必要ですが、防御的なコーディングには十分ではないと思います。

具体的な例:リクエストに一致する値のコレクションを返すように文書化されているサードパーティの検索メソッドについて考えてみます。残念ながら、そのメソッドのドキュメントで明確にされていなかったのは、元の開発者が、要求に一致するものがない場合は、空のコレクションではなくnullを返す方がよいと判断したことです。

だから今、あなたはあなたの防御的でよくユニットテストされたメソッド思考(それは悲しいことに内部nullポインタチェックを欠いている)とブームを呼びます!内部チェックなしでは、以下を処理する方法がないNullPointerException:

defensiveMethod(thirdPartySearch("Nothing matches me")); 
// You just passed a null to your own code.
于 2009-12-16T18:12:10.980 に答える
3

私は「letitcrash」デザインスクールの大ファンです。(免責事項:私は医療機器、航空電子工学、または原子力関連のソフトウェアには取り組んでいません。)プログラムが爆発した場合は、デバッガーを起動してその理由を理解します。対照的に、不正なパラメータが検出された後もプログラムが実行を続ける場合、プログラムがクラッシュするまでに、何が問題だったのかわからない可能性があります。

優れたコードは多くの小さな関数/メソッドで構成されており、これらのコードスニペットのすべてに数十行のパラメーターチェックを追加すると、読みにくく、保守しにくくなります。単純にする。

于 2009-12-16T19:32:36.517 に答える
2

「コードが実行すべきことを検証する単体テスト」>「実行すべきでないことを実行していないことを検証しようとする本番コード」。

公開されたAPIの一部でない限り、自分でnullをチェックすることすらしません。

于 2009-12-16T18:15:36.440 に答える
1

それは非常に異なります。問題のメソッドは、グループの外部のコードによって呼び出されたことがありますか、それとも内部メソッドですか?

内部メソッドの場合、これを論点にするのに十分なテストを行うことができます。目標が可能な限り最高のパフォーマンスであるコードを構築している場合は、正しいと確信している入力のチェックに時間を費やしたくない場合があります。

外部から見えるメソッドの場合(ある場合)、常に入力を再確認する必要があります。いつも。

于 2009-12-16T18:16:25.687 に答える
1

デバッグの観点から、コードがフェイルファストであることが最も重要です。コードが失敗するのが早いほど、失敗のポイントを見つけやすくなります。

于 2009-12-16T19:14:50.797 に答える
0

内部メソッドの場合、通常、これらの種類のチェックのアサートに固執します。これにより、単体テスト(テストカバレッジは良好ですよね?)または少なくともアサーションをオンにして実行されている統合テストでエラーが検出されます。

于 2009-12-16T18:24:04.157 に答える
0

nullポインターのチェック話の半分に過ぎません。また、割り当てられていないすべてのポインターにnull値を割り当てる必要があります。
最も責任のあるAPIも同じことをします。
ヌルポインタのチェックはCPUサイクルで非常に安価になります。アプリケーションが配信されるとクラッシュすることは、あなたとあなたの会社にお金と評判を犠牲にする可能性があります。

コードが完全に制御できるプライベートインターフェイスにある場合、および/または単体テストまたはデバッグビルドテスト(assertなど)を実行してnullをチェックする場合は、nullポインタチェックをスキップできます。

于 2009-12-16T18:36:30.043 に答える
0

この質問では、私が取り上げたいことがいくつかあります。

  1. コーディングガイドラインでは、ポインタを使用する代わりに、参照または値を直接処理するように指定する必要があります。定義上、ポインタはメモリ内にアドレスを保持するだけの値型です。ポインタの有効性はプラットフォーム固有であり、多くのことを意味します(アドレス可能なメモリの範囲、プラットフォームなど)。
  2. 何らかの理由で(動的に生成されたポリモーフィックオブジェクトなど)ポインターが必要になった場合は、スマートポインターの使用を検討してください。スマートポインタは、「通常の」ポインタのセマンティクスで多くの利点を提供します。
  3. たとえば、型が「無効」状態である場合、型自体がこれを提供する必要があります。より具体的には、「未定義」または「初期化されていない」オブジェクトの動作を指定するNullObjectパターンを実装できます(例外をスローするか、no-opメンバー関数を提供することによって)。

次のようなNullObjectのデフォルトを実行するスマートポインタを作成できます。

template <class Type, class NullTypeDefault>
struct possibly_null_ptr {
  possibly_null_ptr() : p(new NullTypeDefault) {}
  possibly_null_ptr(Type* p_) : p(p_) {}
  Type * operator->() { return p.get(); }
  ~possibly_null_ptr() {}
  private:
    shared_ptr<Type> p;
    friend template<class T, class N> Type & operator*(possibly_null_ptr<T,N>&);
};

template <class Type, class NullTypeDefault>
Type & operator*(possibly_null_ptr<Type,NullTypeDefault> & p) {
  return *p.p;
}

次にpossibly_null_ptr<>、デフォルトで派生した「null動作」を持つ型へのnullポインターをサポートする場合は、テンプレートを使用します。これにより、「nullオブジェクト」に許容できる動作があることが設計で明示され、一般的なガイドラインやプラクティスよりも、コードに文書化された防御的なプラクティスがより具体的になります。

于 2009-12-16T19:07:49.310 に答える
0

ポインターは、ポインターを使用して何かを行う必要がある場合にのみ使用してください。一部のデータ構造を横断するためのポインタ演算など。次に、可能であれば、それをクラスにカプセル化する必要があります。

ポインタが関数に渡されて、ポインタが指すオブジェクトで何かを実行する場合は、代わりに参照を渡します。

防御プログラミングの1つの方法は、可能な限りほとんどすべてをアサートすることです。プロジェクトの最初は煩わしいですが、後で単体テストの良い補助になります。

于 2009-12-16T20:19:19.353 に答える
0

多くの回答が、コードに防御を記述する方法の問題に対処していますが、「どの程度防御する必要があるか」についてはあまり語られていません。これは、ソフトウェアコンポーネントの重要度に基づいて評価する必要があるものです。

私たちは飛行ソフトウェアを使用しており、ソフトウェアエラーの影響は、軽微な煩わしさから航空機/乗務員の喪失にまで及びます。コーディング標準やテストなどに影響を与える可能性のある悪影響に基づいて、さまざまなソフトウェアを分類します。ソフトウェアの使用方法とエラーの影響を評価し、必要な(そして余裕のある)防御レベルを設定する必要があります。DO-178B規格では、これを「設計保証レベル」と呼んでいます。

于 2009-12-16T20:42:45.113 に答える