web-dev-qa-db-ja.com

何を優先すべきですか:YAGNIまたはグッドデザイン?

どの時点で [〜#〜] yagni [〜#〜] は適切なコーディング慣行よりも優先されるべきですか?私は職場でプロジェクトに取り組んでいて、同僚に良いコード標準をゆっくりと導入したいのですが(現在はありません。すべてが韻や理由なしに一緒にハッキングされているだけです)、一連のクラスを作成した後( TDDを実行しないでください。悲しいことに、あらゆる種類のユニットテストを実行しないでください。これらのクラスの一部を拡張する必要がないことを確信しているので、一歩戻り、YAGNIに違反していると思いました。

これが私が意味する具体的な例です。私は、基本的なCRUD関数で基本的なリポジトリスタイルのパターンを使用する一連のストアドプロシージャをラップするデータアクセス層を持っています。すべてのリポジトリクラスに必要なメソッドがいくつかあるので、IRepositoryというリポジトリ用の汎用インターフェイスを作成しました。ただし、次に、リポジトリのタイプ(ICustomerRepositoryなど)ごとに「マーカー」インターフェース(つまり、新しい機能を追加しないインターフェース)を作成し、具象クラスがそれを実装します。ストアドプロシージャから返されるDataReaders/DataSetsからビジネスオブジェクトを構築するために、Factory実装でも同じことを行いました。私のリポジトリクラスのシグネチャは次のようになります。

public class CustomerRepository : ICustomerRepository
{
    ICustomerFactory factory = null;

    public CustomerRepository() : this(new CustomerFactory() { }

    public CustomerRepository(ICustomerFactory factory) {
        this.factory = factory;
    }      

    public Customer Find(int customerID)
    {
        // data access stuff here
        return factory.Build(ds.Tables[0].Rows[0]);
    }
}

ここでの懸念は、私がYAGNIに違反していることです。99%の確信を持って、このリポジトリに具体的なCustomerFactory以外のものを与える理由は決してないことを知っています。単体テストがないので、MockCustomerFactoryや同様のものは必要ありません。インターフェイスが多すぎると、同僚を混乱させる可能性があります。一方、ファクトリの具体的な実装を使用すると、デザインのにおいのように見えます。

適切なソフトウェア設計とソリューションの過剰設計をしないで妥協する良い方法はありますか? 「単一の実装インターフェース」をすべて用意する必要があるのか​​、それとも少しだけ良い設計を犠牲にして、たとえばベースインターフェースとその後に単一のコンクリートだけを使用できるのか、そして実装が使用される場合は、インターフェース。

78
Wayne Molina

適切なソフトウェア設計とソリューションの過剰設計をしないで妥協する良い方法はありますか?

YAGNI。

私は少し良いデザインを犠牲にすることができました

誤った仮定。

そしてベースインターフェース、そして単一のコンクリート、

それは「犠牲」ではありません。 is良いデザインです。

73
S.Lott

ほとんどの場合、必要のないコードを避けることでbetterの設計につながります。最も保守可能で将来性のある設計は、要件を満たし、名前が付けられた単純なコードを最小量使用する設計です。

最もシンプルなデザインは、最も進化しやすいものです。無用で高度に設計された抽象化レイヤーのような保守性を損なうものはありません。

78

YAGNIとSOLID(またはその他の設計方法)は相互に排他的ではありません。ただし、これらはほぼ正反対です。どちらにも100%従わなくてもかまいませんが、 -and-take; 1つの場所で1つのクラスが使用する高度に抽象化されたパターンを見て、YAGNIと言ってそれを簡略化すればするほど、SOLIDデザインは少なくなります。逆は、同様に真実です。開発の多くの場合、デザインは「信頼して」しっかり実装されます。どのようにそれを必要とするかはわかりませんが、あなたはただの直感を持っています。これは本当である可能性があります(そしてますます経験が増えるほど本当ですが)、スラップダッシュの「それを軽くする」アプローチと同じくらい多くの技術的負債を負う可能性もあります。DILの「スパゲッティコード」コードベースの代わりに、「バクラバコード」になる可能性があります。 、メソッドまたは新しいデータフィールドを追加するだけの非常に多くのレイヤーがあると、サービスプロキシと1つの実装のみで疎結合された依存関係を通過する1日間のプロセスになります。 「スパゲッティOsコード」を使用すると、アーキテクチャ内で上下左右に移動する小さな緩やかに構成されたピースが提供され、3行ずつ50のメソッドを実行できます。

私は他の答えでそれを言ったが、ここにそれがある:最初のパスでそれを機能させる。2番目のパスでそれをエレガントにする。3番目のパスでそれをソリッドにする。

それを分解する:

コード行を最初に書くとき、それは単に機能する必要があります。この時点で、ご存じのとおり、これは1回限りのものです。したがって、2と2を追加するための「象牙の塔」アーキテクチャを構築するためのスタイルポイントは得られません。やらなければならないことを実行してください。

次にカーソルがそのコード行に移動すると、最初の記述からの仮説が反証されます。あなたはそのコードを再訪していて、それを拡張するか、他の場所で使用する可能性が高いので、1回限りではありません。 DRY(自分で繰り返さないでください)のようないくつかの基本原則とコード設計の他の単純なルールを実装する必要があります;繰り返されるコードのメソッドとフォームループの抽出、一般的なリテラルの変数の抽出または式、いくつかのコメントを追加するかもしれませんが、コード全体は自己文書化する必要があります。これで、コードはしっかりと結合されている場合でも、よく整理されており、それを見る人は誰でも、コードを1行ずつトレースするのではなく、.

カーソルがそのコードに3回目に入ると、おそらく大したことになるでしょう。もう一度拡張するか、コードベースの他の少なくとも3つの異なる場所で使用できるようになります。この時点で、それはシステムのコア要素ではないにしても重要な要素であり、そのように設計する必要があります。この時点で、通常、これまでの使用方法についても理解しているため、設計をどのように設計して、これらの使用法と新しい使用法を合理化するかについて、適切な設計決定を行うことができます。 SOLIDルールは方程式を入力する必要があります。特定の目的を持つコードを含むクラスを抽出し、同様の目的または機能を持つすべてのクラスの共通インターフェースを定義し、クラス間の疎結合依存関係を設定し、設計しますそれらを簡単に追加、削除、または交換できるような依存関係。

この時点から、このコードをさらに拡張、再実装、または再利用する必要がある場合は、すべてがよく知られている「ブラックボックス」形式でパッケージ化され、抽象化されています。他の必要な場所にプラグインするか、インターフェースの使用方法を変更せずに、インターフェースの新しい実装としてテーマに新しいバリエーションを追加します。

64
KeithS

これらのいずれかではなく、WTSTWCDTUAWCROTを使用しますか?

(私たちができる最も簡単なことは何ですか、そして木曜日にリリースすることができますか?)

もっと簡単な頭字語は私のやるべきことのリストにありますが、それらは優先事項ではありません。

YAGNIと良いデザインは相反しません。 YAGNIは将来のニーズをサポートする(しない)ことについてです。優れた設計とは、ソフトウェアが何をするかをどのように透過的にするかということです。

ファクトリーを導入すると、既存のコードが単純になりますか?そうでない場合は、追加しないでください。もしそうなら、例えばあなたがテストを追加するとき(あなたはそうすべきです!)、それを追加してください。

YAGNIは約追加ではない将来の機能をサポートするための複雑さです。
現在のすべての機能をサポートしながら、グッドデザインは約削除複雑さです。

25
Jaap

彼らは対立しておらず、あなたの目標は間違っています。

何を達成しようとしていますか?

高品質のソフトウェアを作成し、それを行うには、コードベースを小さく保ち、問題を発生させないようにします。

競合が発生しましたが、使用しないケースを記述しない場合、すべてのケースをどのようにカバーしますか?

問題は次のとおりです。

enter image description here (興味がある人なら、これを 蒸発する雲 )と呼びます

何が原因なのでしょうか?

  1. 何が必要になるかわからない
  2. 時間を浪費してコードを肥大化させたくない

これらのどれを解決できますか?まあ、それは時間を無駄にしたくないように見え、肥大化したコードは素晴らしい目標であり、理にかなっています。その最初のものはどうですか?コーディングする必要があるものを見つけることができますか?

私は仕事でプロジェクトに取り組んでいて、同僚に良いコード標準をゆっくりと導入したいと考えています(現在は何もなく、すべてが韻や理由なしに一緒にハッキングされているだけです)[...]一歩戻りましたそして、これらのクラスの一部を拡張する必要がないことを確実に知っているので、YAGNIに違反していると考えました。

それをすべて言い換えましょう

  • コード規格なし
  • 進行中のプロジェクト計画はありません
  • カウボーイたちはどこでも自分の気の利いたことをしている(そしてあなたは野生の野生の西で保安官を演じようとしている)、うわー。

適切なソフトウェア設計とソリューションの過剰設計をしないで妥協する良い方法はありますか?

妥協は必要ありません。有能でプロジェクト全体のビジョンを持つチームを管理する人が必要です。あなたが将来必要としないものを投入するのではなく、あなたが必要とするものを計画できる誰かが必要です。なぜなら、あなたは未来についてとても不確かだからです...なぜですか?理由をお話ししましょう。それは、誰もがいまいましい計画を持っていないからです。完全に別の問題を修正するためにコード標準を取り入れようとしています。解決する必要のあるあなたの主要な問題は、明確なロードマップとプロジェクトです。それができたら、「コード標準はチームとしてこの目標をより効果的に達成するのに役立つ」と言えますが、これは絶対的な真実ですが、この質問の範囲外です。

これらのことができるプロジェクト/チームマネージャーを取得します。持っている場合は、マップを要求し、マップがないというYAGNIの問題を説明する必要があります。それらが著しく無能な場合は、計画を自分で書いて、「ここに私たちが必要とするものについての私の報告があります。それを見直して、あなたの決定を知らせてください」と言います。

16
Incognito

質問は誤ったジレンマを提示します。 YAGNI原則の適切な適用は、無関係なものではありません。それは良いデザインの1つaspectです。 SOLID=の各原則は、優れた設計の側面でもあります。あらゆる分野ですべての原則を常に完全に適用できるわけではありません。実際の問題は、コードに多大な影響を及ぼします。設計の原則は、これらすべてを考慮に入れなければなりませんが、すべての状況に対応できる原則はありません。

ここで、それぞれの原則を見てみましょう。それぞれの原則は異なる方向に進むこともありますが、決して本質的に矛盾しているわけではありません。

YAGNIは、開発者が特定の種類のやり直しを回避できるように考案されました。これは、将来変化する、または将来必要になると私たちが考えるものについての仮定または予測に基づいて、誤った決定を早すぎるものにしないようにガイドすることによってこれを行います。集団的経験から、これを行うと通常は間違っていることがわかります。たとえば、YAGNIは、複数の実装者が必要であることを今すぐと知らない限り、インターフェース再利用を目的としてを作成しないように指示します。同様に、YAGNIは、アプリケーションで単一のフォームを管理するための「ScreenManager」を作成しないでください今のところ複数の画面があることを知らない限り。

多くの人が考えていることに反して、SOLIDはnotであり、再利用性、汎用性、さらには抽象化についてです。SOLIDは、 変更の準備済みのコードを記述し、特定の変更が何であるかについては何も言わないでください。SOLIDの5つの原則は、過度に一般的であり、単純ではありません。SOLIDコードの適切な適用により、役割と境界が明確に定義された小さな集中クラスが生成されます。実際の結果は、必要な要件の変更に対して、最小同様に、コードを変更する場合、他のクラスへの「リップル」の量が最小限に抑えられます。

ある状況の例を見て、YAGNIとSOLIDが何を言わなければならないかを見てみましょう。すべてのリポジトリが外部から同じように見えるという事実により、共通のリポジトリインターフェイスを検討しています。しかし、一般的な一般的なインターフェースの価値は、特定の実装者を知る必要なく、実装者のいずれかを使用できることです。これが必要または役立つであろうアプリ内の場所がない限り、YAGNIはしないでくださいと言いますそれ。

5 SOLID確認すべき原則があります。Sは単一の責任です。これはインターフェースについては何も言いませんが、具体的なクラスについては何かを言うかもしれません。データアクセス自体を処理することは議論されるかもしれませんリポジトリの責任は、暗黙的なコンテキスト(CustomerRepositoryはCustomerエンティティの暗黙的なリポジトリ)から、Customerエンティティタイプを指定する一般化されたデータアクセスAPIへの明示的な呼び出しに変換することですが、他の1つ以上のクラスの責任で行うのが最適です。

Oは開閉です。これは主に継承についてです。これは、共通の機能を実装する共通のベースからリポジトリを派生させようとした場合、または異なるリポジトリからさらに派生させることを期待した場合に当てはまります。しかし、そうではないので、そうではありません。

LはLiskov代入性です。これは、共通リポジトリー・インターフェースを介してリポジトリーを使用する予定の場合に適用されます。インターフェースと実装に制限を設けて、整合性を確保し、さまざまなインプリメンターの特別な処理を回避します。これは、そのような特別な処理がインターフェイスの目的を損なうためです。この原則を検討することは、共通リポジトリー・インターフェースの使用を警告する可能性があるため、役立つ場合があります。これはYAGNIのガイダンスと一致しています。

私はインターフェース分離です。これは、リポジトリにさまざまなクエリ操作を追加し始めた場合に当てはまります。インターフェイスの分離は、クラスのメンバーを2つのサブセットに分割できる場合に適用されます。1つは特定のコンシューマーによって使用され、もう1つは他のコンシューマーによって使用されますが、コンシューマーが両方のサブセットを使用することはありません。ガイダンスは、1つの一般的なインターフェースではなく、2つの別個のインターフェースを作成することです。あなたのケースでは、個々のインスタンスのフェッチと保存が、一般的なクエリを実行するのと同じコードによって消費される可能性は低いので、それらを2つのインターフェースに分離すると役立つ場合があります。

DはDependency Injectionです。ここで、Sと同じポイントに戻ります。データアクセスAPIの使用を別のオブジェクトに分離した場合、この原則は、そのオブジェクトのインスタンスを単に更新するのではなく、作成時に渡す必要があることを示しています。リポジトリ。これにより、データアクセスコンポーネントの存続期間を簡単に制御できるようになり、シングルトンにするためのルートをたどらなくても、リポジトリ間でコンポーネントへの参照を共有できるようになります。

SOLIDの原則のほとんどは、アプリ開発のこの特定の段階では必ずしも適用されないことに注意してください。たとえば、データアクセスを開始する必要があるかどうかは、その複雑さによって異なります。データベースに影響を与えずにリポジトリロジックをテストするかどうか(これは(残念ながら、私の意見では)考えられないことなので、おそらく必要ではありません)。

したがって、そのすべての検討の結果、YAGNIとSOLIDは、実際に1つの共通の固体の即時関連のアドバイスを提供することがわかります。共通の汎用リポジトリインターフェイスを作成する必要はおそらくないでしょう。

この注意深い考えはすべて、学習課題として非常に役立ちます。習得には時間がかかりますが、時間が経つと直感が発達し、非常に速くなります。あなたは正しいことを知っていますが、誰かが理由を説明するように求められない限り、これらすべての単語を考える必要はありません。

10
Chris Ammerman

willが必要なため、ユニットテストでコードを拡張できるようにすることは、YAGNIの対象にはなりません。ただし、CustomerFactoryは既にインターフェイスから継承されており、いつでもMockCustomerFactoryと交換できるため、単一実装インターフェイスへの設計変更によってコードのテスト容易性が実際に向上しているとは思いません。

10
DeadMG

「良いデザイン」とは、役に立たなくても常に適用されなければならないある種のアイデアと正式な一連のルールに従うことを意味すると信じているようです。

IMOは悪いデザインです。 YAGNIは優れたデザインのコンポーネントであり、それに矛盾することは決してありません。

6
Vector

一部の人々は、インターフェイス名はIで始めるべきではないと主張します。具体的には、1つの理由は、指定された型がクラスであるかインターフェイスであるかに関する依存関係を実際にリークしていることです。

最初にCustomerFactoryをクラスにして、後でDefaultCustormerFactoryまたはUberMegaHappyCustomerPowerFactory3000によって実装されるインターフェイスに変更することを禁止するものは何ですか。変更する必要があるのは、実装がインスタンス化される場所だけです。そして、もしあなたがより良いデザインを持っていなければ、これはせいぜい一握りの場所です。

リファクタリングは開発の一部です。単一のクラスごとにインターフェースとクラスを宣言し、少なくとも2か所のすべてのメソッド名を同時に変更するように強制するよりも、リファクタリングが容易な小さなコードを使用するほうがよいでしょう。

インターフェイスを使用する本当のポイントは、モジュール性を実現することです。これは、優れた設計の最も重要な柱である可能性があります。ただし、モジュールは外界からの分離によって定義されるだけでなく(それが外の観点からそれを認識する方法であるとしても)、同様にその内部の働きによっても定義されることに注意してください。
私が指摘したいのは、本質的に一緒に属しているものを分離することはあまり意味がないということです。ある意味では、カップごとに個別の棚がある食器棚を持っているようなものです。

重要なのは、大きくて複雑な問題を小さくて単純な副問題に考案することです。そして、あなたはそれらがそれ以上の細分化なしで十分に単純になる点で停止しなければなりません、さもなければそれらは実際により複雑になります。これはYAGNIの当然の結果と見ることができます。そしてそれは間違いなく良いデザインを意味します。

目標は、単一のリポジトリーと単一のファクトリーでローカルな問題を何らかの方法で解決することではありません。目標は、この決定がアプリケーションの残りの部分に影響を与えないことです。それがモジュール性です。
同僚にモジュールを見てもらい、一握りの説明付きのファサードを見て、自信を持って、洗練された内部配管の心配をせずにそれらを使用できるようにしてほしいと思います。

2
back2dos

あなたの例では、YAGNIが勝つべきだと私は言うでしょう。後でインターフェイスを追加する必要がある場合でも、それほどコストはかかりません。ちなみに、まったく目的がなければ、クラスごとに1つのインターフェイスを持つのは本当に良い設計でしょうか。

もう1つ考えてください。必要なのはグッドデザインではなく、十分なデザインである場合があります。以下は、このトピックに関する非常に興味深い一連の投稿です。

2
David

今、漠然と合理的なICustomerRepositoryを書き留めることができますか?たとえば、過去に顧客がPayPalを常に使用したことがある(本当に実際にここに到達している、おそらく悪い例です)?それとも、会社の誰もがアリババとの接続について大騒ぎしていますか?もしそうなら、あなたは今より複雑なデザインを使いたいと思うかもしれません、そしてあなたの上司に遠視されているように見えます。 :-)

それ以外の場合は、お待ちください。実際の実装が1つまたは2つになる前にインターフェースを推測すると、通常は失敗します。言い換えると、一般化する例がいくつかあるまでは、ファンシーなデザインパターンを一般化/抽象化/使用しないでください。

0
user949300

あなたは将来的に複数の実装を見込んでinterfaceを作成しています。 I<class>コードベースのすべてのクラス。しないでください。

YAGNIによると、単一の具象クラスを使用するだけです。テスト目的で「モック」オブジェクトが必要な場合は、元のクラスをabstractクラスにして、2つの実装を使用します。1つは元の具象クラス、もう1つはモック実装です。

明らかに、新しい具象クラスをインスタンス化するには、元のクラスのすべてのインスタンス化を更新する必要があります。静的コンストラクタを前もって使用することで、これを回避できます。

YAGNIは、コードを記述する前にコードを記述しないように指示しています。

優れた設計は抽象化を使用することを言います。

あなたは両方を持つことができます。クラスは抽象化です。

0
Jesse

マーカーがインターフェースする理由「タグ付け」以外の何もしていないことに私は驚かされます。ファクトリータイプごとに異なる「タグ」を使用すると、何がポイントになりますか?

インターフェースの目的は、クラスに「振る舞い」の振る舞いを与え、いわば「リポジトリー能力」をクラスに与えることです。したがって、すべての具象リポジトリタイプが同じIRepositoryのように動作する場合(それらはすべてIRepositoryを実装します)、それらはすべて、まったく同じコードによって、他のコードによって同じ方法で処理できます。この時点で、デザインは拡張可能です。より具体的なリポジトリタイプを追加すると、すべてが一般的なIRepository(ies)として処理されます-同じコードがすべての具体的なタイプを「一般的な」リポジトリとして処理します。

インターフェースは、共通点に基づいて処理するためのものです。ただし、カスタムマーカーインターフェイスa)は動作を追加しません。およびb)それらの一意性を処理するように強制します。

有用なインターフェースを設計する限り、OO具象クラスと1:1の相関関係を持つ特殊なクラス、タイプ、またはカスタムマーカーインターフェースを処理するための特殊なコードを記述する必要がないという利点があります。それは無意味な冗長性です。

たとえば、多くの異なるクラスの強く型付けされたコレクションが必要な場合は、マーカーインターフェイスを確認できます。コレクションでは、それらはすべて「ImarkerInterface」ですが、それらを引き出すときに、それらを適切なタイプにキャストする必要があります。

0
radarbob