あなたの友人は彼の逸話に基づいて多くの逆風に直面しているようです。それは残念であり、働くのは非常に難しい環境です。困難にもかかわらず、彼はパターンを使用して生活を楽にする正しい道を歩んでおり、その道を去ったのは残念です。スパゲッティコードは究極の結果です。
技術面と対人面の2つの異なる問題領域があるため、それぞれ個別に対処します。
対人関係
友人が抱えている苦労は、急速に変化する要件と、それが保守可能なコードを書く能力にどのように影響しているかです。最初に、要件を1日に2回、このような長期間にわたって毎日変更することはより大きな問題であり、非現実的な暗黙の期待があると言います。要件は、コードが変更できるよりも速く変更されています。コードやプログラマが追いつくことを期待することはできません。この急速な変化のペースは、より高いレベルでの望ましい製品の不完全な概念の徴候です。これは問題です。彼らが本当に欲しいものがわからない場合、彼らはそれを手に入れるために多くの時間とお金を無駄にします。
変更の境界を設定することをお勧めします。変更を2週間ごとにまとめてグループ化し、実装中に2週間凍結します。次の2週間の新しいリストを作成します。私は、これらの変更の一部が重複または矛盾していると感じています(たとえば、2つのオプションの間で行ったり来たりする)。変更が迅速かつ激怒すると、それらはすべて最優先されます。それらをリストに蓄積しておけば、作業と生産性を最大化するために最も重要なものを整理して優先順位を付けるためにそれらと協力できます。彼らは彼らの変化のいくつかがばかげているか、それほど重要ではないことに気付くかもしれません。
ただし、これらの問題が原因で適切なコードを記述することを妨げることはありません。悪いコードはより悪い問題につながります。あるソリューションから別のソリューションにリファクタリングするには時間がかかる場合がありますが、それが可能であるというまさにその事実は、パターンと原則を通じて優れたコーディングプラクティスの利点を示しています。
頻繁に変化する環境では、ある時点で技術的負債が発生します。タオルを投げて、大きすぎて克服できないまで待つよりも、それに向かって支払いをする方がはるかに良いです。パターンが役に立たなくなった場合は、リファクタリングしますが、カウボーイのコーディング方法に戻らないでください。
テクニカル
あなたの友人は、基本的なデザインパターンをよく理解しているようです。ヌルオブジェクトは、彼が直面していた問題への良いアプローチです。実際には、それはまだ良いアプローチです。彼が挑戦しているように見えるのは、パターンの背後にある原則、それが何であるのかを理解することです。そうでなければ、彼は彼のアプローチを放棄したとは思わない。
(以下は、元の質問では求められなかったが、説明のためにパターンをどのように順守できるかを示す一連の技術的ソリューションです。)
nullオブジェクトの背後にある原理は、変化するものをカプセル化するという考え方です。変更箇所を隠すため、他の場所で対処する必要はありません。ここでは、nullオブジェクトがproduct.Price
インスタンスの分散をカプセル化していました(これをPrice
オブジェクトと呼びます。nullオブジェクトの価格はになりますNullPrice
)。Price
ドメインオブジェクト、ビジネスコンセプトです。ビジネスロジックでは、価格がまだわからない場合があります。これが起こります。nullオブジェクトの完璧なユースケース。Price
にはToString
、価格を出力するメソッドがあります。不明な場合は空の文字列を出力しNullPrice#ToString
ます(または空の文字列を返します)。これは合理的な動作です。その後、要件が変わります。
null
APIビューに出力するか、マネージャーのビューに異なる文字列を出力する必要があります。これはビジネスロジックにどのように影響しますか?そうではありません。上記のステートメントでは、「ビュー」という単語を2回使用しました。この言葉はおそらく明確に話されていなかったでしょうが、私たちは要件に隠された言葉を聞くために自分自身を訓練しなければなりません。では、なぜ「表示」がそれほど重要なのでしょうか?それは、変化が本当に起こるべき場所を教えてくれるからです。
余談:MVCフレームワークを使用しているかどうかはここでは無関係です。MVCには「表示」という非常に具体的な意味がありますが、プレゼンテーションコードのより一般的な(そしておそらくより適切な)意味でMVCを使用しています。
したがって、ビューでこれを修正する必要があります。どうすればそれができますか?これを行う最も簡単な方法は、if
ステートメントです。nullオブジェクトがすべてのifを取り除くことを意図していたことは知っていますが、実用的でなければなりません。文字列をチェックして空かどうかを確認し、切り替えます:
if(product.Price.ToString("c").Length == 0) { // one way of many
writer.write("Price unspecified [Change]");
} else {
writer.write(product.Price.ToString("c"));
}
これはカプセル化にどのように影響しますか?ここで最も重要な部分は、ビューロジックがビューにカプセル化されることです。このようにして、ビューロジックの変更からビジネスロジック/ドメインオブジェクトを完全に隔離できます。ugいですが、動作します。ただし、これが唯一のオプションではありません。
価格が設定されていない場合にデフォルトの文字列を出力したいという点で、ビジネスロジックが少し変更されたと言えます。Price#ToString
メソッドに微調整を加えることができます(実際にはオーバーロードされたメソッドを作成します)。デフォルトの戻り値を受け入れ、価格が設定されていない場合はそれを返すことができます。
class Price {
...
// A new ToString method
public string ToString(string c, string default) {
return ToString(c);
}
...
}
class NullPrice {
...
// A new ToString method
public string ToString(string c, string default) {
return default;
}
...
}
そして今、私たちのビューコードは次のようになります:
writer.write(product.Price.ToString("c", "Price unspecified [Change]"));
条件はなくなりました。ただし、これをやりすぎると、ドメインオブジェクトに特殊なケースメソッドが増殖する可能性があるため、このインスタンスが少数しかない場合にのみ意味があります。
代わりに、ブール値を返すIsSet
メソッドを作成できPrice
ます。
class Price {
...
public bool IsSet() {
return return true;
}
...
}
class NullPrice {
...
public bool IsSet() {
return false;
}
...
}
ロジックを表示:
if(product.Price.IsSet()) {
writer.write(product.Price.ToString("c"));
} else {
writer.write("Price unspecified [Change]");
}
ビューに条件の戻り値が表示されますが、価格が設定されているかどうかを伝えるビジネスロジックの場合はより強力です。Price#IsSet
現在、他の場所で使用できるようになっています。
最後に、ビューのヘルパーで価格を完全に表示するというアイデアをカプセル化できます。これにより、条件を非表示にし、ドメインオブジェクトを必要なだけ保持します。
class PriceStringHelper {
public PriceStringHelper() {}
public string PriceToString(Price price, string default) {
if(price.IsSet()) { // or use string length to not change the Price class at all
return price.ToString("c");
} else {
return default;
}
}
}
ロジックを表示:
writer.write(new PriceStringHelper().PriceToString(product.Price, "Price unspecified [Change]"));
そこの変化を(我々は一般化可能性にするより多くの方法があるPriceStringHelper
パターンの両方の文字列が空の場合はデフォルトを返すオブジェクトに)が、これらは(大部分は)保存いくつかの簡単なものですと、原則として、そのような変更を行う実用的な側面も同様です。