決して公開されることのないコードの防衛的なプログラミング慣行に従うことはどのくらい必要ですか?


45

私はカードゲームのJava実装を書いているので、ゾーンと呼ぶ特別なタイプのコレクションを作成しました。Javaのコレクションのすべての変更メソッドはサポートされていませんが、ゾーンAPIにmove(Zone, Card)は、指定されたゾーンからそれ自体にカードを移動するメソッドがあります(パッケージプライベートテクニックによって達成されます)。これにより、ゾーンからカードが取り出されず、単に消えることを保証できます。別のゾーンにのみ移動できます。

私の質問は、この種の防御コーディングはどのくらい必要ですか?それは「正しい」ことであり、正しい実践のように感じますが、Zone APIが公共図書館の一部になることは決してありません。それは私だけのためです。したがって、標準のコレクションを使用することでおそらくより効率的になる可能性があるときに、自分からコードを保護しているようなものです。

このゾーンのアイデアをどこまで取り入れるべきですか?誰でも私が書いているクラス、特に実際に公開されないものについては、契約を保存することについてどれだけ考えるべきかについてアドバイスをいただけますか?


4
=〜s / necessary / recommended / gi
GrandmasterB

2
データ型は構造的に正しいはずですが、それ以外の場合は何に基づいていますか?それらは、変更可能かどうかに関係なく、有効な状態にのみなるようにカプセル化する必要があります。これを静的に強制することが不可能な場合(または不当に困難な場合)にのみ、ランタイムエラーが発生します。
ジョンパーディ

1
絶対とは絶対言うな。コードが使用されない限り、コードがどこで終わるかを確実に知ることはできません。;)
イズカタ

1
@codebreaker GrandmasterBのコメントは置換式です。つまり、「必要」を「推奨」に置き換えます。
リカルドソウザ

1
ここでは、コードレスコード#116 誰も信頼しないことが特に適切です。

回答:


72

私はデザインの問題に対処するつもりはありません-非公開のAPIで「正しく」処理するかどうかの問題だけです。

それは私のためだけなので、自分から自分のコードを保護しているようなものです

それがまさにポイントです。たぶん、自分が書いたすべてのクラスとメソッドのニュアンスを覚えていて、間違ったコントラクトで誤ってそれらを呼び出すことのないコーダーがいるでしょう。私は彼らの一人ではありません。私が書いたコードが、それを書いてから数時間以内にどのように動作するかを忘れがちです。一度それを正しく理解したと思うと、あなたの心は取り組んでいる問題にギアを切り替える傾向があります。

あなたはそれと戦うためのツールを持っています。これらのツールには、(順不同で)規則、単体テスト、その他の自動テスト、前提条件チェック、およびドキュメントが含まれます。私自身、単体テストは非常に貴重であることがわかりました。なぜなら、これらはどちらも契約の使用方法を考えさせ、後でインターフェースがどのように設計されたかについてのドキュメントを提供するからです。


知っておくといい。以前は、できるだけ効率的にプログラミングするだけだったので、このようなアイデアに慣れるのに苦労することがあります。正しい方向に進んでくれてうれしいです。
codebreaker

15
「効率的に」とは、さまざまなことを意味します。私の経験では、初心者(私はあなたが1人であると言っているわけではありません)は、多くの場合、プログラムをどれだけ効率的にサポートできるかを見落としています。コードは通常、製品のライフサイクルのサポートフェーズで「新しいコードの作成」フェーズよりもはるかに長い時間を費やすため、これは効率的に慎重に検討する必要があると思います。
チャーリーキリアン

2
私は間違いなく同意します。大学に戻ったとき、それについて考える必要はありませんでした。
codebreaker

25

私は通常、いくつかの簡単なルールに従います。

  • 常に契約でプログラムするようにしてください。
  • メソッドが公開されている場合、または外部から入力を受け取っている場合、いくつかの防御手段を実施します(例IllegalArgumentException)。
  • 内部でのみアクセス可能な他のすべてについては、アサーション(例assert input != null)を使用します。

クライアントが本当にそれに夢中なら、彼らはあなたのコードを誤動作させる方法を常に見つけます。少なくともリフレクションを介していつでもそれを行うことができます。しかし、それが契約によるデザインの美しさです。このようなコードの使用を承認しないため、そのようなシナリオで機能することを保証できません。

あなたの特定のケースに関しては、Zone部外者が使用および/またはアクセスすることになっていない場合、クラスをパッケージプライベートにする(そして可能であればfinal)、またはできればJavaがすでに提供しているコレクションを使用してください。それらはテストされており、車輪を再発明する必要はありません。これにより、コード全体でアサーションを使用して、すべてが期待どおりに動作することを確認できます。


1
契約による設計に言及するための+1。振る舞いを完全に禁止できない場合(そしてそれを行うのは難しい)、少なくとも悪い振る舞いについての保証がないことを明確にします。IllegalStateExceptionまたはUnsupportedOperationExceptionをスローすることも好きです。
user949300

@ user949300もちろん。そのような例外が意味のある目的で導入されたと信じたい。契約を尊重することは、そのような役割に適合するようです。
afsantos

16

防御的なプログラミングは非常に良いことです。
コードの作成の邪魔になるまで。それからそれはそんなに良いことではありません。


もう少し実用的に話す...

あなたは物事をやりすぎの端にいるようです。課題(および質問への回答)は、プログラムのビジネスルールまたは要件を理解することにあります。

カードゲームAPIを例にとると、不正行為を防ぐためにできることすべてが重要な環境がいくつかあります。大量の実際のお金が関係している可能性があるため、不正行為が発生しないことを確認するために、多数のチェックを配置することは理にかなっています。

一方で、SOLIDの原則、特に単一の責任に留意する必要があります。コンテナクラスに、カードの行き先を効果的に監査するように依頼するのは少し大変かもしれません。カードコンテナと移動要求を受信する機能の間に監査/コントローラーレイヤーを配置する方が適切な場合があります。


これらの懸念に関連して、APIのどのコンポーネントが公開されている(したがって脆弱である)か、プライベートで公開されていないコンポーネントを理解する必要があります。私は「内部が柔らかいハード外部コーティング」を全面的に支持しているわけではありませんが、APIの外部を強化するのが最善の努力です。

ライブラリの意図されたエンドユーザーは、どの程度の防御的プログラミングを導入するかを決定するのにそれほど重要ではないと思います。私が自分で使用するために作成したモジュールであっても、ライブラリを呼び出す際に将来私が不注意でミスをしないように、チェックの手段をまだ設けています。


2
「コードの記述を妨げるまで」の+1 特に短期の個人プロジェクトでは、防御的なコーディングは価値があるよりもはるかに時間がかかる可能性があります。
コーリー

2
同意しますが、防衛的にプログラムすることができることは良いことですが、プロトタイピングの方法でプログラムできることも重要です。両方の機能を使用することで、防御的なプログラミング(一種)しかできない私が知っている多くのプログラマーよりもはるかに優れた最適なアクションを選択できます。
デビッドモルダー

13

防御的コーディングは、公開コードにとって良いアイデアではありません。すぐに捨てられないコードにとっては素晴らしいアイデアです。確かに、あなたは呼ばれることになっています方法を知っている、あなたは、あなたが戻ってプロジェクトに来るとき、あなたは今からこの半年を覚えているだろうどれだけ見当がつかない。

Javaの基本的な構文は、それぞれCやJavascriptのような低レベルまたはインタープリター型言語と比較して、多くの焼き付けられた防御を提供します。メソッドに明確な名前を付け、外部の「メソッドシーケンス」を持たないと仮定すると、おそらく正しいデータ型として引数を指定し、適切に型付けされたデータがまだ無効である場合の賢明な動作を含めることで回避できます。

(余談ですが、カードを常にゾーンに配置する必要がある場合、プレイ中のすべてのカードをゲームオブジェクトにグローバルなコレクションで参照し、ゾーンをしかし、私はあなたのゾーンがカードを保持する以外に何をするのか分からないので、それが適切かどうかを知るのは難しいです。)


1
ゾーンはカードのプロパティであると考えましたが、カードは不変オブジェクトとして機能するため、この方法が最適であると判断しました。アドバイスをありがとう。
codebreaker

3
@codebreakerその場合に役立つことの1つは、カードを別のオブジェクトにカプセル化することです。スペードのエースはそれです。場所はその身元を定義せず、カードはおそらく不変であるべきです。ゾーンにカードCardDescriptorが含まれている可能性があります。カード、その場所、フェイスアップ/ダウンステータス、またはそれを重視するゲームのローテーションが含まれている可能性があります。これらはすべて、カードのIDを変更しない可変プロパティです。

1

まず、ゾーンまたはその中のカードをなくさないように、ゾーンのリストを保持するクラスを作成します。その後、転送がZoneList内にあることを確認できます。インスタンスは1つだけ必要なので、このクラスはおそらく一種のシングルトンになりますが、後でゾーンのセットが必要になる可能性があるため、オプションを開いたままにしてください。

第二に、ZoneまたはZoneListにCollectionを実装させたり、必要と思わない限り他のものを実装させたりしないでください。つまり、ZoneまたはZoneListがCollectionを予期するものに渡される場合、それを実装します。例外(UnimplementedException、またはそのようなもの)をスローさせるか、単に何もしないようにすることで、多数のメソッドを無効にすることができます。(2番目のオプションを使用する前に一生懸命に考えてください。簡単だからやってみれば、早い段階で見つけたかもしれないバグが見つからないことがわかります。)

何が「正しい」かについての本当の質問があります。しかし、それが何であるかを理解したら、そのように物事をしたいと思うでしょう。2年後にはこのすべてを忘れてしまいます。コードを使用しようとすると、そのような直感に反した方法でコードを記述し、何も説明しなかった男に本当にイライラします。


2
あなたの答えは、OPが一般的な防御的プログラミングについて尋ねているより広範な質問ではなく、当面の問題に焦点を合わせすぎています。

私は実際にコレクションを取得するメソッドにゾーンを渡すので、実装が必要です。ただし、ゲーム内のゾーンの一種の登録は興味深いアイデアです。
コードブレーカー

@ GlenH7:特定の例で作業することは、抽象的な理論よりも役立つことが多いと思います。OPはかなり興味深いものを提供してくれたので、私はそれで行きました。
ラルフシャピン

1

API設計における防御コーディングは、一般に入力を検証し、適切なエラー処理メカニズムを慎重に選択することに関するものです。他の回答が言及していることも注目に値します。

実際には、これはあなたの例の目的ではありません。非常に特定の理由で、APIサーフェスを制限しています。以下のようGlenH7は言及カードのセットが(「使用」と「未使用」)デッキ、例えば、テーブルや手で、実際のゲームで使用する場合、あなたは間違いなく必ず各作るために場所にチェックを入れたいですセットのカードは一度だけ存在します。

「ゾーン」を使用してこれを設計したことは、任意の選択です。実装に応じて(上記の例ではゾーンは手、デッキ、またはテーブルのみになります)、完全な設計になる可能性があります。

ただし、その実装は、よりCollection<Card>制限の少ないAPIを備えた、より類似した一連のカードの派生型のように聞こえます。たとえば、手元の計算機やAIを構築する場合、繰り返し処理する各カードの数と数を自由に選択できます。

そのため、そのAPIの唯一の目的が各カードが常にゾーン内にあることを確認することである場合、このような制限のあるAPIを公開するのは良いことです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.