私はほとんどの場合「クリーンなコード」を書こうとします。しかし、実際にはそれは非常に難しいことを意味します。つまり、徐々にビジネス要件が劇的に変化するか、ビジネス要件が既存のコードを変更するための単なる条件のように見えます。
「変更」アプローチではなく「拡張」に進むと、実装はクリーンに見えます。しかし、ユースケースの多くのクラス階層は単純に思えます。さらに、「変更」を使用してコードの数行を記述するだけで、「拡張」アプローチを使用してクラスのセットをいくつか記述するのではなく、特定の「時間」の問題を修正できる場合があります。
コードを最初に埋め込んだときではなく、数年後にコードをクリーンに保つ方法。具体的には、時間が重要でビジネスにとって新しい要件は「ほんのわずかな変更」です。
どうしても必要な場合を除いて、Open/Closedの原則(OCP)には従わないでください。ほとんどのアプリケーション開発では、OCPは適切ではなく、すでに気づいているように、コードが非常に複雑になるだけです。
OCPは、コードを使用する独立した外部クライアントがあり、下位互換性を維持しながらコードを進化させる必要があるシナリオに適しています。 .netフレームワークを使用してください。MSは、フレームワーク内の既存のAPIの動作を変更することはできません。これは、現在の動作に応じてすべてのクライアントが破壊されるためです。 APIの拡張のみが可能です。 OCPは実際にはクリーンなコードにつながりません。たとえば、フレームワークには、削除できない決して非推奨のAPIが複数あります。ただし、下位互換性は維持されます。
別のシナリオは、変更ごとにシステム全体に予期しない副作用があり、リグレッションから保護するための自動テストがない「泥沼のような」アーキテクチャがある場合です。その場合、偏執的な考え方を採用し、既存のコードを壊さないようにコードを「閉じた」状態に保つことができます。しかし、ここでコードを変更しないことは症状を処理するだけであり、それ自体はコードの品質を向上させません。可能であれば、問題の根本原因を処理することをお勧めします。
OCPの原則に従う必要がない場合は、動作を拡張するよりも動作を変更する方が、多くの場合、よりクリーンで簡単です。
すべてのプログラミングガイドラインと同様に、OCは普遍的なルールではなく、特定の状況でのみ適切です。
OCPがすべてのビジネス変更でコードを変更できないようにすることを期待していますか?要件が「自転車を貸すアプリケーションを提供する」から「車を盗むアプリケーションを提供する」に変更された場合、どのルールもあなたを救いません。
では、なぜOCPに従う必要があるのでしょうか。なぜそれが役立つのですか?戦略パターンはOCPの力を説明する最も簡単な方法だと思います。いくつかの販売プロセスの割引を計算するシステムの一部を考えてみましょう。割引は注文額と購入するユーザー(VIP、レギュラーなど)に依存すると仮定します。 1つのアプローチは次のとおりです。
public calculateDiscount(Customer customer, Order order) {
// some common part
Discount d;
if(customer.isVip(){
d = // some code
} else if(customer.isRegular()){
d = // some code
}
// etc.
// some common code
}
別のもの:
public calculateDiscount(Customer customer, Order order) {
// some common part
DiscountPolicy dp = customer.getDiscountPolicy();
Discount d = dp.calculateOn(order);
// some common code
}
別のタイプの顧客がいる場合はどうなりますか?最初のアプローチでは、コードを変更する必要があります。これにより、calculateDiscount
の動作が壊れますか?エラーを発生させずに変更を加えることができますか?私は知っています、この簡単な例ではおそらくはい;)
ただし、将来的に変更または拡張される(継承の意味ではなく、ユースケースの別のニュアンスをカバーするという意味で)すでに知っているか、強く感じるシステムの多くの部分があります。 コードをエラーから防ぐをできるだけ多くする必要があります。そのような防止の方法の1つは機能するコードを変更しないです。 OCPはすべて、機能するコードを変更しないことを目的としています。 Strategy、Decorator、Abstract Factory、Commandなどのパターンはすべて、コードの一部をOCPに準拠させることです。
明らかに、禁止されていない変更があります。市場タイプに応じて割引を計算するための要件を追加すると、calculateDiscount
メソッド内の契約とコードだけでなく、ユニットテストコードやその他のテストも変更されます。それは避けられません。しかし、OCPは「しわくちゃのゾーン」を提供します。現実世界のように-与えられたくしゃくしゃのゾーンは、私たちが遭遇するクラッシュには必ずしも十分ではありません。
OCPに従うことは、OOPプログラマーの目標の1つである必要があります。この原則を無視すると、オブジェクトに分解することがいかに便利かがわかりません。「彼らはただそれを書き直すことができる変更に反応します。」.
OCPは、古いコードを変更するのではなく、新しいコードを追加することによって変更を許可する設計を支持するように求めます。
このための構造メカニズムは、設計パターンのように複雑にすることも、変数を導入するのと同じくらい簡単にすることもできます。抽象化は、使用している詳細が見えない場合に最適です。
最も重要な問題は、アイデアを別のオブジェクトに配置することによって、あるアイデアを別のアイデアから分離するのに適していると思われる場合です。これを行うと、OCPに違反するだけでなく、理由もなく余分なクラスを入力したことになります。アイデアをさまざまなオブジェクトに分割する場合、それらは互いに死を握り合うべきではありません。
ただし、変化を予測することは困難です。 Yagniは、役に立つかもしれないものを作成するのではなく、今日役立つものだけを作成するように教えています。
将来を予測するのではなく、何かが安定すると仮定するときは保守的にしてください。私たちの高レベルの抽象化、つまりインターフェースは、それらが変わるときに本当に私たちを傷つけます。安定していると彼らが想定していることを最小限にとどめてください。確信が持てないものを低いレベルにプッシュします。それらを分割して脆弱性を維持し、フットプリントを小さくします。マスターは1人だけにしてください。
これを行うと、次のOCPが大幅に異なってはなりません。ただし、すべてのオブジェクトのインターフェースを平手打ちし、ポリモーフィックでないものを呼び出さない場合は、OOPコーダーに悪い名前を付けていることになります。
ここで適用できる最良のアドバイスは、実際にDRY違反にいつ反応するかということでした。2回目に繰り返すと、最初に正しく分解する可能性がはるかに高くなります。すぐに反応しないでください。
この知恵は、変化を予測するのがいかに迅速かを和らげるはずです。単体テストは、実装の詳細をどのように変更できるかを確認するのに役立ちます。これは優れた構造演習ですが、単体テストは製品コードではありません。彼らが物事がどのように変化するかをあなたに示すように注意深く考えてください。
ただし、機能を追加したり、既存のコードを変更してバグを修正したりするたびに気分が悪くなるはずです。実際に独立した外部クライアントがなくても、あたかもコードの大規模なスワスをサポートできる場合、それは本当に素晴らしいことです。
これらの原則は、コードをより高速に動作させるのに役立つわけではありません。一度実行すると、コードを引き続き機能させることができます。そのため、変更が加えられるまで、それらを追跡することに時間を費やして時間を浪費しているかどうかはわかりません。それらを適用する練習をしたい場合は、固定された仕様にコーディングすることはできません。驚かれるような仕様の変更に合わせてコーディングする必要があります。
これらの原則を適用する保守的な方法は、予測ではなく変更への対応に複雑さを追加することです。この方法をとると、モジュール内の既存のコードを1回、おそらく2回変更しなければならないことを許します。その後、それが再び起こるのを止めることについて見る時が来ました。
それ以外の場合は、手続き型プログラミングに戻ることもできます。毎回書き直したいのであれば、変更に対処することもできるからです。