私は同僚の 1 人と、コードがどの程度防御的であるべきかについて話し合っていました。私は防御的なプログラミングを得意としていますが、どこでやめるべきかを知っておく必要があります。私たちは他の人によって維持されるプロジェクトに取り組んでいますが、これは、開発者が行う可能性のあるクレイジーなことをすべてチェックする必要があるという意味ではありません. もちろん、それを行うこともできますが、これによりコードに非常に大きなオーバーヘッドが追加されます。
線を引く場所をどうやって知るのですか?
私は同僚の 1 人と、コードがどの程度防御的であるべきかについて話し合っていました。私は防御的なプログラミングを得意としていますが、どこでやめるべきかを知っておく必要があります。私たちは他の人によって維持されるプロジェクトに取り組んでいますが、これは、開発者が行う可能性のあるクレイジーなことをすべてチェックする必要があるという意味ではありません. もちろん、それを行うこともできますが、これによりコードに非常に大きなオーバーヘッドが追加されます。
線を引く場所をどうやって知るのですか?
ユーザーが直接的または間接的に入力するものはすべて、常に健全性をチェックする必要があります。それを超えて、assert
あちこちにいくつかの s があっても害はありませんが、とにかく、クレイジーなプログラマーがコードを編集したり壊したりすることについては、実際にはあまりできません!-)
私は、言語に基づいてコードに入れる防御の量を変更する傾向があります。現在、私は主に C++ で作業しているため、私の考えはその方向に漂っています。
C++ で作業する場合、十分な防御的プログラミングはありません。私は自分のコードを核の秘密を守っているかのように扱い、他のすべてのプログラマーはそれらを手に入れようとしています。アサート、スロー、コンパイラ タイム エラー テンプレートのハッキング、引数の検証、ポインターの削除、詳細なコード レビュー、および一般的なパラノイアはすべて公正なゲームです。C++ は、私が愛していると同時にひどく不信感を抱いている邪悪で素晴らしい言語です。
私は「防御的プログラミング」という用語のファンではありません。私には、次のようなコードが示唆されています。
void MakePayment( Account * a, const Payment * p ) {
if ( a == 0 || p == 0 ) {
return;
}
// payment logic here
}
これは間違っています、間違っています、間違っていますが、私は何百回も見たに違いありません。この関数は、最初からヌル ポインターを使用して呼び出されるべきではありません。
ここでの正しいアプローチは議論の余地がありますが、最小限の解決策は、アサートを使用するか、例外をスローすることによって、うるさく失敗することです。
編集:ここでの他の回答やコメントには同意しません-すべての関数がパラメーターをチェックする必要があるとは思いません(多くの関数では、これは単に不可能です)。代わりに、すべての関数が許容できる値を文書化し、他の値が未定義の動作をもたらすことを述べる必要があると私は信じています。これは、これまでに作成された中で最も成功し、広く使用されているライブラリである C および C++ 標準ライブラリで採用されているアプローチです。
そして今、反対票を始めましょう...
これに答える方法が本当にあるのかどうかはわかりません。経験から学ぶものばかりです。潜在的な問題がどの程度一般的であるかを自問し、判断を下す必要があります。また、常に防御的にコーディングする必要があるとは限らないことも考慮してください。場合によっては、コードのドキュメントに潜在的な問題を書き留めるだけでも問題ありません。
結局のところ、これは人が自分の直感に従わなければならないものだと思います。正しい方法も間違った方法もありません。
コンポーネントのパブリック API に取り組んでいる場合は、十分な量のパラメーターの検証を行う価値があります。これにより、どこでも検証を行う習慣ができました。それは間違いです。これらすべての検証コードがテストされることはなく、システムが必要以上に複雑になる可能性があります。
今は単体テストで検証することを好みます。検証は、外部ソースからのデータに対しては確実に行われますが、外部以外の開発者からの呼び出しに対しては行われません。
私の個人的なイデオロギー:プログラムの防御力は、潜在的なユーザーベースの最大の素朴さ/無知に比例する必要があります。
私は常に自分の仮定を Debug.Assert します。
API コードを使用する開発者に対して防御することは、通常のユーザーに対して防御することとそれほど違いはありません。
それ以上は、問題が発生した場合にアプリが正常に回復することを確認し、開発者が何が起こっているのかを理解できるように常に十分な情報を開発者に提供することを除いて、他に行うことはあまりありません。
システムには、防御チェックが行われる適切に設計された境界が必要です。ユーザー入力を検証する場所(境界)と、その他の潜在的な防御問題をチェックする必要がある場所(たとえば、サードパーティの統合ポイント、公開されているAPI、ルールエンジンの相互作用、またはプログラマーのさまざまなチームによってコーディングされたさまざまなユニット)について決定する必要があります。 )。それよりも防御的なチェックは多くの場合DRYに違反し、ごくわずかな利益のためにメンテナンスコストを追加するだけです。
そうは言っても、あなたがあまり妄想的になれない特定のポイントがあります。バッファオーバーフロー、データ破損、および同様の問題の可能性は、非常に厳密に防御する必要があります。
私は最近、ユーザー入力データがリモート ファサード インターフェイス、ローカル ファサード インターフェイス、その他のクラスを介して伝達され、最終的に実際に使用されたメソッドに到達するというシナリオがありました。私は自分自身に質問をしていました:いつ値を検証する必要がありますか? 値が実際に使用される最終クラスにのみ検証コードを追加しました。伝播パス上にあるクラスに他の検証コード スニペットを追加することは、私にとって防御的なプログラミングになりすぎます。リモート ファサードは例外の 1 つかもしれませんが、これもスキップしました。
防御的プログラミングは、契約ごとに設計する方法で契約を守る唯一の方法です。
他の2つは
もちろん、開発者が行う可能性のあるクレイジーなことすべてから身を守るべきではありませんが、前提条件を使用して期待されることをどのコンテキストで実行するかを述べる必要があります。
//precondition : par is so and so and so
function doSth(par)
{
debug.assert(par is so and so and so )
//dostuf with par
return result
}
テストも作成しているかどうかという問題を持ち込む必要があると思います。コーディングでは防御的であるべきですが、JaredPar が指摘したように、使用している言語にも依存すると思います。アンマネージ コードの場合は、非常に防御する必要があります。それが管理されていれば、少し余裕があると思います。
テストがあり、他の開発者がコードをデシメートしようとすると、テストは失敗します。ただし、コードのテスト カバレッジに依存します (存在する場合)。
良い質問です。私は、サニティ チェックを行うか行わないかの間でバタバタしていました。その50/50
状況では、私はおそらく次のようなルーチンのみを「防弾」するという中間の立場を取ります。
(a) プロジェクト内の複数の場所から呼び出される
(b) 変更される可能性が高いロジックを持っている
(c) デフォルト値を使用できない
(d) ルーチンは正常に「失敗」することはできません
暗い夜
私は防御的ではなく、まったく敵対的なコードを書こうとします。何か問題があり、それを修正できる場合は、修正します。そうでない場合は、例外をスローまたは渡し、他の誰かの問題にします。ファイル システム、データベース接続、ネットワーク接続など、物理デバイスと対話するものはすべて、信頼性が低く、障害が発生しやすいと見なす必要があります。これらの障害を予測してトラップすることが重要です
この考え方を身につけたら、重要なのはアプローチに一貫性を持たせることです。コールチェーンの問題を伝えるためにステータスコードを返すことを期待していますか、それとも例外が好きですか。混合モデルはあなたを殺すか、少なくともあなたを飲酒に駆り立てます。重く。他の誰かのAPIを使用している場合は、これらのものを、使用する用語でトラップ/レポートするメカニズムに分離します。これらのラッピング インターフェイスを使用します。
ここでの議論が、将来の (おそらく悪意のある、または無能な) メンテナに対して防御的にコーディングする方法である場合、できることには限界があります。テスト カバレッジと仮定の主張の自由な使用を通じて契約を強制することは、おそらくあなたができる最善の方法であり、理想的にはコードを混乱させず、将来の非悪のメンテナーの仕事を難しくしない方法で行う必要があります。コード。アサートは読みやすく理解しやすく、特定のコードの前提が何であるかを明確にするため、通常は優れたアイデアです。
ユーザーのアクションに対して防御的にコーディングすることは、まったく別の問題であり、私が使用するアプローチは、ユーザーが私を捕まえようとしていると考えることです。すべての入力は管理できる限り慎重に調べられ、コードがフェイル セーフになるようにあらゆる努力を払っています。厳密に吟味されていない状態を永続化しないようにし、可能な場合は修正し、できない場合は適切に終了するなどしてください。外部エージェントによってコードで実行される可能性のあるすべてのボゾについて考えるだけで、正しい考え方が得られます。
プラットフォームや他のモジュールなど、他のコードに対して防御的にコーディングすることは、ユーザーとまったく同じです。OS は常に不適切なタイミングでスレッドをスワップ アウトし、ネットワークは常に不適切なタイミングで機能しなくなります。一般に、あらゆる場所に悪があふれています。すべての潜在的な問題に対してコードを作成する必要はありません。メンテナンスのコストは、安全性の向上に見合うものではないかもしれませんが、それについて考えてみても害はありません。また、考えたシナリオが何らかの理由で重要ではないと考える場合は、通常、コード内で明示的にコメントしても問題はありません。