私はJavaカードゲームの実装)を書いているので、ゾーンと呼ぶ特別なタイプのコレクションを作成しました。Javaのコレクションのすべての変更メソッドはサポートされていませんが、ゾーンAPIであるmove(Zone, Card)
は、カードを指定されたゾーンからそれ自体に移動します(パッケージプライベートテクニックによって達成されます)。それらは別のゾーンにのみ移動できます。
私の質問は、この種の防御的コーディングはどの程度必要かということです。これは「正しい」ものであり、適切なプラクティスのように感じられますが、Zone APIがパブリックライブラリの一部になることはありません。それは私のためだけのものなので、標準のコレクションを使用するだけで効率が上がる可能性があるときに、自分からコードを保護しているようなものです。
このゾーンのアイデアはどこまで取ればよいですか?私が書いたクラスのコントラクトを保持することについて、特に実際には公開されないコントラクトを保持することについて、どの程度考慮すべきかについて誰かが私にいくつかのアドバイスを与えることはできますか?
私は設計の問題に取り組むつもりはありません-非公開のAPIで「正しく」物事を実行するかどうかの問題だけです。
それは私のためだけなので、自分のコードを自分から保護しているようなものです
それがまさにポイントです。多分そこに彼らが書いたすべてのクラスとメソッドのニュアンスを覚えていて、間違った契約でそれらを誤って呼び出さないコーダーがそこにいるでしょう。私はその一人ではありません。私が書いたコードが、それを書いてから数時間以内にどのように機能するかを忘れてしまいがちです。一度正しく理解できたと思った後、あなたの心はあなたが取り組んでいる問題にギアを切り替える傾向があります今。
あなたはそれと戦うためのツールを持っています。これらのツールには、(順不同で)規則、単体テストおよびその他の自動テスト、前提条件チェック、およびドキュメントが含まれます。私自身、ユニットテストは非常に貴重であると感じました。どちらも、契約がどのように使用されるかを考えさせ、後でインターフェイスの設計方法に関するドキュメントを提供するように強制するからです。
私は通常、いくつかの単純なルールに従います。
IllegalArgumentException
など)を適用します。assert input != null
)。クライアントが本当にそれに関与している場合、彼らは常にあなたのコードを誤動作させる方法を見つけます。彼らは常に、少なくとも反射を通してそれを行うことができます。しかし、それが契約による設計の美点です。このようなコードの使用を承認しないため、そのようなシナリオでコードが機能することを保証できません。
特定のケースについて、Zone
が外部のユーザーによって使用および/またはアクセスされることが想定されていない場合は、クラスをpackage-private(およびfinal
)にするか、できれば、コレクションJavaはすでに提供されています。これらはテスト済みであり、ホイールを再発明する必要はありません。これにより、コード全体でアサーションを使用してすべてが機能することを確認できます。予想通り。
防御的プログラミングはとても良いことです。
コードの記述の邪魔になるまで。それはそれはそんなに良いことではありません。
もう少し実用的に話す...
あなたは物事を遠くまで追い詰めようとしています。課題(および質問への回答)は、プログラムのビジネスルールまたは要件が何かを理解することにあります。
例としてカードゲームAPIを使用すると、不正行為を防止するために実行できるすべてのことが重要な環境がいくつかあります。大量のリアルマネーが関与する可能性があるため、チートが発生しないことを確認するために多数のチェックを配置することは理にかなっています。
一方、SOLID=原則、特に単一の責任に注意する必要があります。カードがどこに行くのかを効果的に監査するようにコンテナクラスに依頼することは、少し多くなるかもしれません。カードコンテナーと移動要求を受信する関数の間に監査/コントローラーレイヤーがあります。
これらの懸念に関連して、APIのどのコンポーネントが公開されている(したがって脆弱である)か、何が非公開で公開されていないかを理解する必要があります。私は「内側が柔らかい外側のハードコーティング」を完全に擁護するわけではありませんが、APIの外側を固めることが最善の方法です。
私は、ライブラリーの意図されたエンドユーザーが、どれだけ多くの防御的プログラミングを導入するかについて決定を下すことほど重要ではないと思います。私が自分で使用するために作成したモジュールを使用する場合でも、ライブラリーの呼び出しで将来私が不注意でミスを犯していないことを確認するための対策を講じています。
防御的コーディングは、公開コードに適しているだけではありません。それはすぐに捨てられないコードのための素晴らしいアイデアです。確かに、それがどのように呼ばれるべきかはわかっていますnowですが、プロジェクトに戻ったとき、この6か月後のことをどれだけ覚えているかはわかりません。
Javaの基本構文は、CやJavaScriptのような低レベルまたはインタープリター型言語と比較して、多くの防御機能を提供します。メソッドに明確な名前を付け、外部の「メソッドの順序付け」がないと仮定すると、引数を正しいデータ型として指定し、適切に入力されたデータが依然として無効である可能性がある場合は、賢明な動作を含めるだけで済むでしょう。
(余談ですが、カードが常にゾーンにある必要がある場合は、プレイ中のすべてのカードをゲームオブジェクトのグローバルコレクションによって参照させ、ゾーンを各カード。しかし、私はあなたのゾーンがカードを保持する以外に何をするのかわからないので、それが適切であるかどうかを知るのは難しいです。
最初にゾーンのリストを保持するクラスを作成して、ゾーンまたはその中のカードを失わないようにします。次に、転送がZoneList内にあることを確認できます。インスタンスは1つしか必要ないため、このクラスはおそらく一種のシングルトンになりますが、後でゾーンのセットが必要になる可能性があるため、オプションは開いたままにしておきます。
次に、必要がある場合を除いて、ZoneまたはZoneListにCollectionなどを実装させないでください。つまり、ZoneまたはZoneListがコレクションを期待するものに渡される場合、その後がそれを実装します。一連のメソッドを無効にするには、それらに例外(UnimplementedExceptionなど)をスローさせるか、単に何もしないようにします。 (2番目のオプションを使用する前に真剣に考えてください。簡単な方法で実行すると、早い段階で発見できたはずのバグが見つからないことがわかります。)
何が「正しい」のかについて、本当の質問があります。しかし、それが何であるかを理解したら、その方法で物事をやりたいと思うでしょう。 2年後にはこれらすべてのことを忘れてしまいます。コードを使用しようとすると、そのような直感に反する方法でコードを記述し、何も説明しなかった人に本当にイライラします。
API設計における防御的コーディングは、一般に、入力を検証し、適切なエラー処理メカニズムを慎重に選択することです。他の回答が言及していることも注目に値します。
これは実際にはあなたの例の目的ではありません。非常に特定の理由により、APIサーフェスが制限されています。 GlenH7 が言及しているように、カードのセットが実際のゲームで使用される場合、(「使用済み」と「未使用」)デッキ、テーブル、手など、あなたは間違いなくセットの各カードが1度だけ存在することを確認するために所定の場所でチェックします。
これを「ゾーン」で設計したかどうかは任意です。実装に応じて(ゾーンは上記の例では手、デッキ、またはテーブルのみにすることができます)、完全な設計になる可能性があります。
ただし、その実装は、より制限の少ないAPIで、よりCollection<Card>
に似たカードのセットの派生型のように聞こえます。たとえば、ハンドバリューカルキュレーターまたはAIを構築する場合、反復する各カードの数と枚数を自由に選択できるようにする必要があります。
そのため、そのようなAPIの唯一の目的が各カードが常にゾーンにあることを確認することである場合は、そのような制限的なAPIを公開することをお勧めします。