マーカスパンとヤグニ

画像 最近、私たちのニュースフィードに2人のヒーロー、プログラマーとパン屋が登場しました。ボリスとマーカスです。 ボリスは良い人で完璧主義者であり、マーカスは目立たない非常に謙虚で灰色のプログラマーです。 どちらも最善を尽くして努力し、奉仕したいと考えています。 しかし、マーカスは一生懸命努力しなかったようです。

これは新しいブランチです-続き。 今日、ストーリーはマーカスにのみ触れています。 彼は主人公です。

だから、物語は切り捨てられています。



元の投稿: 2人のプログラマがパンを焼いた方法



エントリー



最初の投稿では、ボリスに多くの注意が払われ、マーカスにはほとんど注意が払われていなかったと思いました。 おそらく彼の謙虚さのためです。 投稿は楽しかったです。私と多くの人は、コメントから判断して、気に入っています。 そして、私は彼らが建築宇宙飛行士を撃ったことを非常にうれしく思います。 そして、マーカスは勝利の地位にありました。 しかし、それは最初の一撃に過ぎず、スコープを設定する時でした。 元の投稿にはトリックがありました-ボリスがしたことは完全に明らかにされ、マーカスがしたことは外部インターフェイスの背後に残っていました。 ひどいスパゲッティコードがあると暗黙のうちに想定されていました。



今日は、マーカスの位置を修復しようとします。 投稿はYAGNIの原則に専念します。 これはYAGNIの使用例であり、個人的な経験に基づいています。 残念ながら、この原則を適用する方法を例で示す本は多くありません。 そして、ほとんどのプログラマーは、本を読むことに加えて、経験と仕事を通してこれらのスキルを生みます。 これらは単なる理論ではなく、コードスキルだと思います。 そして、そのような経験は共有するのがいいでしょう。 あなたから何か新しいことも学べたら嬉しいです。 この投稿は練習専用であり、タスクはC#で検討されます。 残念ながら、潜在的な視聴者を減らすことができます。 しかし、理論自体は本当の可能性を見るまで敵に受け入れられないので、他の方法はありません。 気に入らないUMLクラス図。 そして、マーカスにとっては、明らかに、それらは必要ありませんでした。



また、C#のスタイルとコードの見落としにあまり注意を払わないようお願いします。 私は理解しているように、アプローチの本質を示したいだけです。 そして、ささいなことに注意を向けないでください。 また、TDDアプローチは示しません。単体テストの書き方も示しません。そうしないと、このような単純なタスクであっても、投稿は非常に膨大になります。 もちろん、TDDはコードに対して独自の調整を行っていました。 しかし、元の投稿から見たように、MarkusがYAGNIを使用していた場合、Markusがそれほどひどくなっていたかどうかにのみ興味があります。



そしてもちろん、私はささいなことを書きます。 コメントで判断すると、多くの人が私と同じように考えており、彼らはそのような投稿を悪化させることはありません。 また、いくつかはさらに優れています(機能的なアプローチを使用)。



始めましょう。 要件のチェーン全体を見ていきましょう。 私はマーカスです。 確かに、私は少し異なるマーカスであり、以前のものとまったく同じように動作しません。



要件1

-みんな、パンを作る必要があります



分析



パンを作るには、メソッドが必要です。 パンとは? これは特定のエンティティです。 これはオブジェクトではありません。そうでなければ、他のオブジェクトと混同される可能性があります。 これは、int型またはその他の組み込み型または作成済みの型ではありません。 パンです。これは新しい別個のクラスです。 彼には症状や行動がありますか? 個人的には、生地(小麦粉、小麦、ライ麦など)で構成されていること、店舗で購入できること、食べられることを知っています。 しかし、これは私の個人的な知識です。 顧客は、行動や状態について言葉を発言していません。 そして以来 私は怠け者です。もう少し知っていても、顧客が何を望んでいるかを直接示すことなく、ボタンを再び押すことはありません。



要件からのみ進みます。パンには状態と動作がなく、パンを取得する方法も必要です。 残念ながらまたは幸いなことに、C#言語自体には、もう少し大騒ぎが必要です。つまり、任意のクラスでメソッドを定義するためです。 しかし、以来 顧客はこれについて一言も言わなかったので、名前を気にしません。インスタンスを気にしません。今のところ静的メソッドを作成することにしました。 どちらかといえば、いつでもやり直す時間があるでしょう。 要件の最も平均的な理解に一致する名前を選択します。 したがって、最初のコードは次のとおりです。



class Bread { } class BreadMaker { public static Bread MakeBread() { return new Bread(); } }
      
      







要件2

-パンを作るだけでなく、オーブンで焼く必要があります



分析



顧客は、自分が望むものを言うのではなく、何かを実装する方法を示しようとすることがあります。 お客様の実装の希望は、身体的または心理的に私に影響を与えません。 この場合、これは要件ではありません。 顧客は私がパンをどこで手に入れたかを確認する方法がありません。 そして、彼がそのような願望さえ表明するまで-チェックする。 したがって、パンを受け取って彼に渡す方法は、彼の顧客ビジネスではありません。 しかし、私は需要に丁寧に同意し、彼らが怠idleのためにさらにお金を払ってうれしいです。



まだ何もしていません。 しかし、念のため、ストーブを覚えています。 顧客はオーブンも、その違いも、パンへの異なる影響も示さなかった。 後者も重要です。 顧客がオーブンのいくつかのタイプを示したとしても、とにかく急ぐ場所はありません-パンは同じです。



しかし、それでも、私たちはボスのために楽しいものを作り、意味に合うようにコードを少し修正します。 つまり、パンはオーブンで焼き、店では買わないことをすでに知っています。 breadメソッドの名前を変更するだけです:



 class BreadMaker { public static Bread BakeBread() { return new Bread(); } }
      
      







要件3

-ガスなしでは焼けないようにするにはガスストーブが必要です



分析



ああ! 新しい情報が到着しました。 ガス炉があり、その動作は他の炉とは異なることが判明しています。 確かに、他の炉のテーマは再び開示されていません。 いいでしょう それらを異なるものにします。



いくつかの実装を比較してみましょう。



実装1。

 enum Oven { GasOven, OtherOven } class BreadMaker { public static double GasLevel { get; set; } public static Bread BakeBread(Oven oven) { return oven == Oven.GasOven && GasLevel == 0 ? null : new Bread(); } }
      
      







副作用はすぐに明らかになります。 ガスレベルは、BakeBread()の呼び出しとは別に設定されます。 ギャップが生じると、バグが出現する可能性の広い分野が開きます。 これらのバグ(バグ)の出現は、畑に損害を与える可能性があります。そうすれば、小麦もパンもなくなります。



このようにパラメーターを個別にインストールすると、コードのユーザー(およびこの犠牲ユーザーになる可能性があります)は、ガス炉を開始する前にガスレベルを設定することを忘れてしまう可能性があります。 そして、以前にパンを焼いたとき、ガスレベルはオーブンの前の設定のままになる場合があります。 本当に忘れてしまうと、予測不可能な動作につながります。



また、プロパティが静的であることもわかります。 これも非常に悪いことです。ガスレベルは1つしかありません。 ただし、静的メソッドと静的プロパティを削除しても上記の問題は解決されないため、このオプションは考慮しません。



実装2。



 enum Oven { GasOven, OtherOven } class BreadMaker { public static Bread BakeBread(Oven oven, double gasLevel) { return oven == Oven.GasOven && gasLevel == 0 ? null : new Bread(); } }
      
      







とても簡単です。 そして、以前の実装よりも少し良いです。 ただし、一貫したパラメーターが常にBakeBread()メソッドに渡されるとは限りません。 非ガス炉の場合、gasLevelは意味をなしません。 メソッドは機能しますが、非ガス炉のgasLevelのタスクは、コードのユーザーを混乱させます。 また、コンパイル段階ではパラメーターの正確性はチェックされません。



実装3.パラメータに同意するためには、炉をクラスのように見せなければなりません。

さらに、通常のパンは常にパンを焼きますが、ガスは常にそうではありません。 つまり 2つのクラス、仮想メソッド、オーバーロード。 ただし、オーブンが独自に作成されないように、それらの修飾子にアクセスする方法を考える必要がありますが、私のBakeBread()メソッドを使用します。そうしないと、副作用が発生します。



そして、それは私(マーカス)で夜明けします! この段階で、これを行うだけです:



 class BreadMaker { public static Bread BakeBreadByGasOven(double gasLevel) { return gasLevel == 0 ? null : new Bread(); } public static Bread BakeBreadByOtherOven() { return new Bread(); } }
      
      







確かに、顧客はまだどのようにストーブを使用するかという言葉を言っていません。 この段階でのこのようなコードは非常に満足のいくものです。



要件4

-オーブンでパイ(別々に-肉、別々に-キャベツ)、およびケーキを焼くことができるようにする必要があります。



分析



はい、質問はありません! オーブンで温度レベルを設定すると、アイスクリームを焼くことができます。 冗談。 マーカス、私は真剣になりましょう-温度についての言葉ではありません。 誰があなたを知っているか、顧客))



だから、パイとケーキ。 それだけでなく、2種類のパイ。 しかし、これは、肉のパイとキャベツのパイにはケーキよりも多くの共通点があることを人生から知っています。 しかし、タスクのコンテキストでは、顧客はこれについて話しませんでした。 彼は、どうにかしてパイを別々に、ケーキを別々にグループ化するとは言わなかった。 したがって、これまでのところ、要件に基づいて-ケーキはほとんどチェリーのパイのように振る舞います-それらはすべて同じです。 彼らは何か行動がありますか? いや 条件はありますか? いや したがって、それらを互いに区別するには、列挙を作成するだけで十分です。 そして、明日発生する顧客の希望を推測して先に進むために、私たちは基本的に望んでいません。 だから-リストが最も正しいです。 最も可能性が高い。 わからない。 しかし、しないでください。 その場合は、いつでも書き換えが可能です。



並行して、名前を変更しました。今はパンではなくベーカリー製品を焼いています。



 public enum BakeryProductType { Bread, MeatPasty, CabbagePasty, Cake } public class BakeryProduct { public BakeryProduct(BakeryProductType bakeryProductType) { this.BakeryProductType = bakeryProductType; } public BakeryProductType BakeryProductType { get; private set; } } class BakeryProductMaker { public static BakeryProduct BakeByGasOven(BakeryProductType bakeryProductType, double gasLevel) { return gasLevel == 0 ? null : new BakeryProduct(bakeryProductType); } public static BakeryProduct BakeByOtherOven(BakeryProductType breadType) { return new BakeryProduct(breadType); } }
      
      







要件5

-さまざまなレシピに従ってパン、パイ、ケーキを焼く必要があります



分析



コードをざっと見てみると、BakeryProductTypeのすばらしいリストがあることがわかります。 サブジェクトエリアの近くではなく、なんとなく不器用に、何らかの方法でプログラム的に呼び出されます。 しかし、それはレシピのように振る舞います。 何が起こるか! パンとロールは、タイプではなく、レシピに従って焼きます。 そして、おそらく、レシピはロールデザイナーに伝わります。 名前を変更するのに十分。 唯一の障害はお団子財産です。 しかし、私は自分自身を辞任したでしょう。 コードを機械的に見て、サブジェクト領域をいくつかのセットとして想像すると、レシピとタイプの間に大きな違いは見られません。 つまり レシピは、後で起こることの直接の理由です。 もちろん、生活の中で、私たちはレシピについてもう少し知っています-彼らは何が起こるかを記述するだけではありません。 また、取得アルゴリズムも含まれています。 しかし、誰が気にしますか? 顧客はこれについて話しましたか? いや これは、タスクのコンテキストではこれが発生しなかったことを意味します。 アルゴリズムが必要です-後でバインドし、何かを考え出します。

したがって、プロパティは型のままであり、列挙はレシピのままであるという事実に我慢します。 話し言葉の特性のため、相続人や別のリストを作成しないでください。 タスクのコンテキストでは、すべてが正確です。 あまり美しくありませんが。 妥協?



 public enum Recipe { Bread, MeatPasty, CabbagePasty, Cake } public class BakeryProduct { public BakeryProduct(Recipe recipe) { this.BakeryProductType = recipe; } public Recipe BakeryProductType { get; private set; } } class BakeryProductMaker { public static BakeryProduct BakeByGasOven(Recipe recipe, double gasLevel) { return gasLevel == 0 ? null : new BakeryProduct(recipe); } public static BakeryProduct BakeByOtherOven(Recipe recipe) { return new BakeryProduct(recipe); } }
      
      







要件6

「レンガをオーブンで焼く必要があります。」



分析



この要件とすべての書面による要件を文字通り守れば、レンガはケーキやパンと変わりません。 おもしろいことに、私たちのレンガはジャムパイとははるかに異なります。要件にそれが含まれていないためです。 パンのように。 したがって、YAGNIによるこの要件は大幅に誇張されており、すべてのクラスの名前を変更してレシピの列挙を拡張することによってのみ実現されます。これは、レンガなどの「オーブン製品」のロールです。 クラスアーキテクチャを作成する方法の全体的なポイントは、その使用方法です。 これは、一般的と見なされるもの(つまり、基本クラスの状態と動作)およびプライベート(継承者の状態と動作)からのものです。 どちらも存在しない場合、列挙も可能です。 推測しないことは怖くない。 クラスと相続人への列挙は簡単に変換されます。



コードに恐怖がありましたか? このコードをテストするのは難しいかもしれませんか? はい、ボリスのコードはテストするのがはるかに難しいようです。 ボリュームは、より多くのテストです。 必要以上の機能がありますか? より多くのテスト。

もちろん、明らかに、元の投稿は、要件がより詳細であり、各フレーズが詳細な説明で明確にされたことを暗示していました。 しかし、YAGNIジャンルは考えないことを要求します。



要件をさらに試しましょう。



要件7

「どうして見なかったの?」 すべてのストーブがレンガを燃やすことができるわけではありません。 これを行うには、特別なオーブンが必要です。



分析



いいでしょう 列挙からブリックを削除し(レシピ?)、名前を返します。 別の空のBrickクラスを作成します。 そして新しい方法:



 public static Brick MakeBrickByFurnace() { return new Brick(); }
      
      







ところで、柔軟性が今必要ない場合は、誰もが何らかの種類のオブジェクトを正確に生成する豊富なメソッドが、オブジェクトを作成する柔軟な方法よりも優れています。 柔軟性が必要でない場合、プログラムは許可を少なくし、制限を増やす必要があります。 特定のタイプのオブジェクトをインターフェイスに置き換えることがしばしば便利なユニットテストは考慮しません。 このコードはすべて、場合によっては簡単にインターフェイスに変換されます。 はい、また、C#を反映したものは、ある種のデカップリングのテストではそれほど要求されません。



さらに、顧客はマーカスと対戦することにしました。



要件8

-各レシピには、製品とその量(重量)を含める必要があります。 需要のレシピが添付されています。



分析



ボリスが待っていた最初の血。



ひどいスパゲッティコードに対処してみましょう。これは、ここで長い間形成されていたはずであり、リファクタリングする機会を与えてくれません。 そうですか?



レシピの製品は明らかにリストされています。 レシピ自体には、作成するものの名​​前(または同じもの、それ自体の名前)だけでなく、数量と製品のセットも既に含まれています。 しかし、同時に、製品の厳密なセットが特定のレシピに関連付けられており、変更されていないことに気付きます。 (もう一度、YAGNIについての投稿を思い出します-「予備金を変更したい場合はどうでしょう!」いいえ、突然-今日-これは今日、そして明日-これは明日です)。



つまり 顧客は、レシピの製品と重量が異なる可能性があるとは言わなかった。 もちろん、彼はそれらを修正すべきだとは言わなかった。 しかし、固定されたケースはより限定的で厳密です。 そして、より限られたケースを常に選択します。 私たちにとっては、柔軟性を高めるのではなく、よりシンプルで厳格な方が良いのです。



そして、厳密な製品セットを備えたレシピは、個人的な体験により良く対応しています。 この場合、継承を使用して各レシピのクラスを記述することは実用的ではありません。 その後、各クラスは定数のみを保存します。



そしてさらにいくつかの考え。 なぜなら 現時点では、コード内のレシピは指定された列挙にすぎず、コンパイル前に設定されています。他に要件がない場合は、明らかにこの動作が残っているはずです。 これから、すべてのレシピが利用可能になり、コードで直接設定されることになります。 列挙型拡張機能なしで新しいものを作成することはできません。 ここから、RecipeNameで同じ名前の列挙の名前を変更した後、Recipeクラスを作成する必要があるようです。 世界は非常に不安定です。 現在、リストにはレシピのみが示されており、レシピを選択することはできますが、完全には特徴付けられていません。



上記の条件を満たすには、次のようにします。



 public enum RecipeName { Bread, MeatPasty, CabbagePasty, Cake } public enum RecipeProduct { Salt, Sugar, Egg, Flour } public class Recipe { private Recipe() { } public RecipeName Name { get; private set; } public IEnumerable<KeyValuePair<RecipeProduct, double>> Products { get; private set; } private static Dictionary<RecipeName, Dictionary<RecipeProduct, double>> predefinedRecipes; static Recipe() { predefinedRecipes = new Dictionary<RecipeName, Dictionary<RecipeProduct, double>> { { RecipeName.Bread, new Dictionary<RecipeProduct, double> { {RecipeProduct.Salt, 0.2}, {RecipeProduct.Sugar, 0.4}, {RecipeProduct.Egg, 2.0}, {RecipeProduct.Flour, 50.0} } } .................. }; } public static Recipe GetRecipe(RecipeName recipeName) { Recipe recipe = new Recipe(); recipe.Name = recipeName; recipe.Products = predefinedRecipes[recipeName]; return recipe; } }
      
      







何も壊す必要はありません。 製品を作成するには、今のところレシピの名前で十分です。 そこでは何も変更しません。 それが必要になります、我々はレシピ自体を渡します。



このコードでは、レシピ一覧からクラスを作成し、レシピの名前とそれを構成する製品を関連付けました。 「ねじ込み」できるように、レシピでアクションのシーケンスを表現する必要があります。 これが明確で、スパゲッティコードがないことを願っています。 クラスごとに別の動作が表示されます。Recipeクラスが簡単に基本クラスになり、相続人が表示されます。 しかし、私たちはそれについて考えません。 YAGNIがありますが、これを行うように言われていません。 しかし、私たちはこれを恐れていません。



要件9

計画外のアプローチを知った下品な顧客は、それをキャッチすることにしました。

「レシピを変えてほしい。」 ストーブは、料理人が作成したレシピに従って調理されました。



論争に入る:

-変更方法

-料理人はレシピを知らず、実験できると信じています。 レシピを修正しましたか? そして、彼は異なる量の卵、砂糖などを追加したいと考えています。

-そして、この場合、どのようなパンを手に入れますか? 何か手に入れるべき? パン、ケーキ、またはケーキ? 明らかに、レシピが異なる場合、料理人は何か他のものを焼きます。

-ケーキは味が異なり、甘く、甘くないと思います。 パンも。 そのため、レシピはある程度異なる場合がありますが、リストから製品を入手します。

-つまり 何が得られるのかを知るには、料理人のレシピの製品リストに最も近いレシピを探す必要がありますか?

-はい。



分析



レシピを修正しました。 現在、レシピは修正されていない可能性があります。 しかし、私たちが持っているものはベンチマークです。 コードのユーザーが独自のレシピを作成できるようにするには、コンストラクターを開いてください。 しかし、製品を尋ねる機会を与えることも必要です。 プロパティをユーザーに割り当てたり、特定のタイプを指定したりする機会を与えたくありません。 そうでなければ、彼は私たちの基準を損なうことができます。 したがって、最も簡単な方法は、デザイナーに製品を転送する機会を与えることです。 また、作成と初期化の間のギャップをなくし、バグの可能性を減らします。



これで2つのコンストラクターができました。



 private Recipe() { } public Recipe(IEnumerable<KeyValuePair<RecipeProduct, double>> products) { Dictionary<RecipeProduct, double> copiedProducts = new Dictionary<RecipeProduct, double>(); foreach (KeyValuePair<RecipeProduct, double> pair in products) { copiedProducts.Add(pair.Key, pair.Value); } this.Products = copiedProducts; }
      
      







2番目のコンストラクターはコピーを作成します。 これは、C#プロパティ-デフォルトのリンクを渡すためです。 クラスのユーザーにはリンクがあり、コピーを作成しない場合は、後でレシピの材料を変更できます。 計画に含まれていないもの。



また、この投稿では、標準OOPのフレームワーク内に留まり、ラムダとzheneriksを使用しないようにしています。 より多くの聴衆が私がしていることを理解できるように。 コードは異なる方法で簡単に記述できます。 しかし、私の目標は、YAGNIの原理自体とコードを評価するいくつかの方法を説明することであり、シャープのさまざまな可能性を示すことではありません。 もちろん、評価方法は言語とその機能に依存します。



ユーザー用の2番目のコンストラクターは、プロパティの値(レシピの名前)を設定しません。 なぜなら 私たちの製品はデザイナーに渡され、変更することはできません。そこで、そこから何らかの方法で近接度を計算します。 より正確には、特に「スマート」は変化する可能性がありますが、パラノイアに悩むことはありません。 開発者は適切であり、破壊ではなく創造の立場にあると信じています。



何らかの近接メソッドを書く必要があります。 顧客は指定しなかったので、最小二乗法で最も単純なものを記述します。 各成分に異なる「重量」があることを考えると。 今のところ、後で調整できる重みを書き留めておきます。



コードはほぼ次のようになります。



 private double GetDistance(Recipe recipe) { Dictionary<RecipeProduct, double> weights = new Dictionary<RecipeProduct, double>(); weights[RecipeProduct.Salt] = 50; weights[RecipeProduct.Sugar] = 20; weights[RecipeProduct.Egg] = 5; weights[RecipeProduct.Flour] = 0.1; double sum = 0.0; foreach(KeyValuePair<RecipeProduct, double> otherProductAmount in recipe.Products) { var productAmounts = this.Products.Where(p => p.Key == otherProductAmount.Key); if (productAmounts.Count() == 1) { sum += Math.Pow(productAmounts.First().Value - otherProductAmount.Value, 2) * weights[otherProductAmount.Key]; } else { return double.MaxValue; } } return sum; } private RecipeName GetRecipeName() { IEnumerable<Recipe> etalons = ((RecipeName[])Enum.GetValues(typeof(RecipeName))) .Select(recipeName => Recipe.GetReceipt(recipeName)); IEnumerable<KeyValuePair<RecipeName, double>> recipeNamesWithDistances = etalons .Select(e => new KeyValuePair<RecipeName, double>(e.Name, GetDistance(e))); double minDistance = recipeNamesWithDistances.Min(rd => rd.Value); if (minDistance == double.MaxValue) { throw new Exception("   "); } return recipeNamesWithDistances.First(rd => rd.Value == minDistance).Key; }
      
      







そして、それぞれコンストラクター呼び出しで、命名が追加されます:



 public Recipe(IEnumerable<KeyValuePair<RecipeProduct, double>> products) { Dictionary<RecipeProduct, double> copiedProducts = new Dictionary<RecipeProduct, double>(); foreach (KeyValuePair<RecipeProduct, double> pair in products) { copiedProducts.Add(pair.Key, pair.Value); } this.Products = copiedProducts; this.Name = GetRecipeName(); }
      
      







常にオンザフライで計算する方が信頼性が高くなります。 しかし、その場合は、参照用と非固定レシピ用の命名の分離を考え出す必要があります。 今のところこれで十分です。



ご覧のとおり、このような現在の要件を満たすためにコードをやり直すことはまったく怖いことではありません。 何も壊しませんでした そしてちょうど拡大しました。 事前に予測していた場合よりも、完了する時間はありません。 しかし、推測ではなく予測することは本当に怖いです。 この要件に従って、一見シンプルに見えるこのコードを想像してみてください。 これは、追加テストが必要な単なるモンスターです。 そして今、それは合理的に書かれています。



コードは完璧ではありません、私は助けることができませんでしたが、zhenerikiとlambdaに突入します。 そのため、コードはより小さく、よりきれいになります。 これにより、C#やラムダに慣れていない読者の理解が損なわれないことを願っています。 もちろん、さらに減らすことができます。 しかし、私はより多くの人々に理解されようとしています。



ここでは、特定のアルゴリズムに既に関連付けられていますが、この顧客はこれを必要としませんでした。 ヤグニかどうか? ここで状況を理解します。 おそらく、顧客はすぐに目に見える結果を必要とします。 これはよく起こります。 したがって、少なくともいくつかのアルゴリズムが必要です。 しかし、後で別のアルゴリズムが必要になった場合、このアルゴリズムを別のアルゴリズムに置き換えるのに費用はかかりません。 または、いくつかを書いて選択することもできます。 または、コードのユーザーからでも、近接性を比較するデリゲートを取得します。



明確なビジネス、今では製造方法にレシピの名前ではなく、レシピ自体を転送する必要があります。 つまり このように:



 public class BakeryProduct { public BakeryProduct(Recipe recipe) { this.BakeryProductType = recipe.Name; } public RecipeName BakeryProductType { get; private set; } }
      
      







そして:



 public static BakeryProduct BakeByGasOven(Recipe recipe, double gasLevel) { return gasLevel == 0 ? null : new BakeryProduct(recipe); } public static BakeryProduct BakeByOtherOven(Recipe recipe) { return new BakeryProduct(recipe); }
      
      







ここで、リファクタリングは過剰なストレスからはほど遠い。



要件10

油断ならない顧客はどういうわけか私たちのコードを研究し、私たちのコードが完全に準備されていない最も苦しい場所を探しています。 9 . . .



— , , , . , . , , , , . ( ))), , .. , .



分析



, . , . . , , ? , , . , . , , . , . ? . すべきではない。



, – . . , , – , . , , . . Product. Product? . . , , . , , . . つまり , , . , . , – . ? .



. , . つまり . , , , , ..



つまり – , .



-.

, , : « , . , ( ). . , .»



. – .



 public abstract class Article { public double Price { get; private set; } public Article(double price) { this.Price = price; } }
      
      







:



 public class BakeryProduct : Article { public BakeryProduct(Recipe recipe, double price): base(price) { this.BakeryProductType = recipe.Name; } public RecipeName BakeryProductType { get; private set; } } public class Brick: Article { public Brick(double price) : base(price) { } }
      
      







, , .



このようなもの:



 public static BakeryProduct BakeByOtherOven(Recipe recipe, double price) { return new BakeryProduct(recipe, price); }
      
      







. . , . , . , – . , , . , .. ( , ..) , . , – .



switch , , , , . . . . , . , .



, , . . . , , . . . . . , . YAGNI , . , , . - YAGNI . .



, . . , , , YAGNI . . . , — .



All Articles