質問

可能な複製:
防御プログラミング

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

一部の人々は、ヌルポインターの小切手だけが必要であると感じました。私は、それが通過するすべての方法ではなく、より高いレベルでチェックできるかどうか、そしてNullのチェックは、ポイントの反対側のオブジェクトが特定の要件を満たしていないかどうかを非常に限られたチェックであることを疑問視しました。

Nullの小切手は何もないよりも優れていることを理解し、同意しますが、Nullのみをチェックすることは範囲が制限されているため、誤ったセキュリティの感覚を提供すると感じています。ポインターが使用可能であることを確認したい場合は、null以上のことを確認してください。

このテーマに関するあなたの経験は何ですか?下位のメソッドに渡されるパラメーターのコードにディフェンスをどのように記述しますか?

役に立ちましたか?

解決

コードComplete 2では、エラー処理に関する章では、バリケードのアイデアを紹介されました。本質的に、バリケードはコードであり、すべての入力が入力されることを厳密に検証します。バリケード内のコードは、無効な入力がすでに処理されていること、および受信された入力が良いと想定できます。バリケード内では、コードはバリケード内の他のコードによって渡される無効なデータを心配する必要があります。条件と賢明な単体テストを主張すると、バリケードコードに対する自信が高まる可能性があります。このようにして、あなたはバリケードで非常に防御的にプログラムしますが、バリケードの中ではあまりプログラムしません。それについて考えるもう1つの方法は、バリケードでは常にエラーを正しく処理し、バリケードの内部ではデバッグビルドで単に条件を主張することです。

生のポインターを使用する限り、通常、できる最善のことは、ポインターがヌルではないと主張することです。そのメモリにあるはずのものが何があるかを知っているなら、内容が何らかの形で一貫していることを確認できます。これは、そのメモリが一貫性自体を確認できるオブジェクトに包まれていない理由の問題を招きます。

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

これらの質問に答えることは、より守備的になる方法を見つけるのに役立ちます。これは、防御しやすいデザインになるということです。

他のヒント

防御する最良の方法は、実行時にヌルのポインターをチェックすることではなく、 そもそもヌルである可能性のあるポインターを使用しないでください

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

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

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

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

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

真剣において、それはあなたがあなたに与えなければならないバグの数に依存します。

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

要するに、あなたがあなたのコードを呼ぶすべての人があなたの人生を台無しにするために最善を尽くすだろうと仮定した場合、あなたはおそらく最悪の犯人を見つけるでしょう。

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

具体的な例:リクエストに合った価値のコレクションを返すように文書化されたサードパーティ検索方法を検討してください。残念ながら、その方法のドキュメントでは明確ではなかったのは、元の開発者が、リクエストと一致していない場合は空のコレクションではなく、ヌルを返す方が良いと判断したことです。

それで、あなたはあなたの防御的でよくテストされたメソッド思考(悲しいことに内部ヌルポインターチェックが欠けている)とブームを呼び出します! nullpointerexceptionは、内部チェックがなければ、次の方法を扱う方法がないということです。

defensiveMethod(thirdPartySearch("Nothing matches me")); 
// You just passed a null to your own code.

私は「Let It Crash」のデザイン学校の大ファンです。 (免責事項:私は医療機器、アビオニクス、または原子力関連ソフトウェアに取り組んでいません。)プログラムが爆発した場合、デバッガーを起動して理由を理解します。対照的に、違法なパラメーターが検出された後、プログラムが実行され続けると、クラッシュするまでに、おそらく何が問題になったのかわからないでしょう。

優れたコードは、多くの小さな関数/方法で構成されており、コードのスニペットのすべてにパラメーターチェックのダース行を追加すると、読み取りが難しく、維持が難しくなります。複雑にしないでおく。

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

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

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

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

の可能な値は何ですか foobar ?まあ、それは何によってだけに制限されています std::string 含まれる可能性があります...これはかなり曖昧です。

一方で:

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

はるかに優れています!

  • 人々が誤って議論の順序を逆転させると、それはコンパイラによって検出されます
  • 各クラスは、価値が正しいことを確認することに責任を負います。ユーザーは負担をかけられません。

私は強いタイピングを好む傾向があります。アルファベット順の文字のみで構成され、最大12文字までのエントリがある場合は、小さなクラスを包むことをお勧めします。 std::string, 、シンプルで validate 割り当てを確認するために内部的に使用され、そのクラスを代わりに渡します。このようにして、検証ルーチンを一度テストした場合、その値が私に到達できるすべてのパスについて実際に心配する必要はないことを知っています>それが私に到達したときに検証されます。

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

また、例外なくルールはありません。公開されたインターフェイスは、必ずしも検証コードで肥大化しています。ただし、自己検証オブジェクトがBOMにある場合、一般的には非常に透明です。

「コードが行うべきことを確認する単位テスト」>「生産コードが、それが想定されていないことをしていないことを確認しようとしている」。

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

それは非常に依存します。問題の方法は、グループの外部でコードによって呼び出されたことがありますか、それとも内部方法ですか?

内部の方法については、これをMOOTポイントにするのに十分なテストを行うことができます。目標が最も高いパフォーマンスであるコードを構築している場合、かなり正しい入力をチェックするのに時間を費やしたくないかもしれません。

外部から表示されるメソッドの場合 - ある場合は、常に入力を再確認する必要があります。いつも。

デバッグの観点から見ると、コードがフェイルフーストであることが最も重要です。コードが早期に失敗するほど、障害のポイントを簡単に見つけることができます。

内部の方法については、通常、これらの種類のチェックについてアサートに固執します。これにより、ユニットテストでエラーが発生します(テストカバレッジが適切ですよね?)、または少なくともアサーションで実行されている統合テストでは、エラーが発生します。

ヌルポインターの確認ストーリーの半分だけ, 、あなたもそうする必要があります 割り当てられていないすべてのポインターにヌル値を割り当てます.
最も責任あるAPIは同じことを行います。
ヌルポインターのチェックは、CPUサイクルで非常に安くなり、配信された後にアプリケーションがクラッシュすると、あなたとあなたの会社がお金と評判を犠牲にする可能性があります。

コードがプライベートインターフェイスにあるかどうかをnullポインターチェックをスキップできます。また、ユニットテストまたはデバッグビルドテストを実行してnullを完全に制御したり、nullをチェックしたりできます(Assertなど)

この質問には、ここに取り組んでいることがいくつかあります。

  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オブジェクト」に許容可能な動作があることが明確になり、これにより、一般的なガイドラインや慣行よりもコードに記録され、より具体的な防御慣行が記録されます。

ポインターを使用する必要があります。ポインターで何かをする必要がある場合にのみ。ポインター算術など、いくつかのデータ構造を横断します。可能であれば、クラスにカプセル化する必要があります。

ポインターが関数に渡され、それが指すオブジェクトを使用して何かを行う場合、代わりに参照を渡します。

防御プログラミングの1つの方法は、できることをほとんどすべてに主張することです。プロジェクトの開始時には迷惑ですが、後にそれは単体テストの良い補助です。

多くの回答は、あなたのコードで防御を書く方法の質問に扱っていますが、「あなたはどれほど防御すべきか?」について多くは言われていません。これは、ソフトウェアコンポーネントの重要性に基づいて評価しなければならないものです。

私たちはフライトソフトウェアと、軽度の迷惑から航空機/乗組員の喪失に至るまでのソフトウェアエラーの影響を行っています。コーディング標準、テストなどに影響を与える潜在的な悪影響に基づいて、さまざまなソフトウェアを分類します。ソフトウェアの使用方法とエラーの影響を評価し、必要なレベルの防御(および余裕がある)を設定する必要があります。 DO-178B標準 これを「設計保証レベル」と呼びます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top