いくつの設計パターンと抽象化レベルが必要ですか?[閉まっている]


29

ソフトウェアに抽象化が多すぎて設計パターンが多すぎる、またはその逆を言うには、どうすればもっと多くの抽象化が必要かを知ることができますか?

私が協力している開発者は、これらの点に関して異なる方法でプログラミングしています。

いくつかの小さな機能をすべて抽象化し、可能な限りデザインパターンを使用し、いかなるコストでも冗長性を回避します。

私を含む他の人は、より実用的であることを試み、すべての設計パターンに完全に適合するわけではありませんが、適用される抽象化が少ないため、理解がはるかに速いコードを記述します。

これはトレードオフであることを知っています。プロジェクトに十分な抽象化が行われていることをどのように確認できますか?

例、Memcacheを使用して汎用キャッシングレイヤーを作成する場合。私たちは本当に必要ですかMemcacheMemcacheAdapterMemcacheInterfaceAbstractCacheCacheFactoryCacheConnector、...またはこれは、保守が容易とまだ良いコードは、これらのクラスの半分しか使用している場合ですか?

Twitterでこれを見つけました:

ここに画像の説明を入力してください

https://twitter.com/rawkode/status/875318003306565633


58
設計パターンをバケツから引き出してプログラムのアセンブルに使用するものとして扱っている場合、あまりにも多く使用しています。
Blrfl


5
それは、人々が使用するスピーチパターンのようなパターンを設計すると考えることができます。ある意味で、イディオム、メタファーなどはすべてデザインパターンだからです。すべての文のイディオムを使用している場合...それはおそらくあまりにも頻繁です。しかし、彼らは考えを明確にし、さもなければ散文の長い壁になるものの理解を助けることができます。「どれくらいの頻度で比phorを使用すべきか」と答える正しい方法はありません。それは著者の判断​​次第です。
プライム

8
1つのSEの回答でこのトピックを適切にカバーする方法はありません。これには、文字通り長年の経験と指導が必要です。これは明らかに広すぎる。
jpmc26

5
航空業界の基本的な設計原則、「簡素化とさらなる軽量化」に従ってください。バグを修正する最良の方法は、バグを含んでいるコードを単に削除することであることがわかった場合、バグがなくてもまだ何も役に立たないため、設計を正しくし始めています!
アレフゼロ

回答:


52

食事には何個の材料が必要ですか?車両を組み立てるのに何個の部品が必要ですか?

実装のわずかな変更がコード全体の変更のカスケードにつながる場合、抽象化が少なすぎることがわかります。適切な抽象化は、変更が必要なコード部分の分離に役立ちます。

インターフェースが少し変更されても、さまざまなレベルでコード全体に変更のカスケードが発生する場合、抽象化が多すぎることがわかります。2つのクラス間のインターフェイスを変更する代わりに、プロパティを追加したり、メソッド引数のタイプを変更したりするために、多数のクラスとインターフェイスを変更します。

それを除けば、実際に数字を与えて質問に答える方法はありません。抽象化の数は、プロジェクトごと、言語ごと、さらに開発者ごとに同じではありません。


28
2つのインターフェイスとそれらを実装する数百のクラスしかない場合、インターフェイスを変更するとカスケードの変更につながりますが、インターフェイスが2つしかないため、抽象化が多すぎるというわけではありません。
Tulainsコルドバ

抽象化の数は、同じプロジェクトの異なる部分でも同じではありません!
T.サール-復活モニカ

ヒント:Memcacheから別のキャッシュメカニズム(Redis?)への変更は、実装の変更です。
オーガ詩sal 33

Tulainsが示しているように、2つのルール(ガイドライン、呼び方は何でも)は機能しません。彼らはまた、たとえそうであっても悲惨なほど不完全です。投稿の残りの部分は無回答であり、合理的な範囲の回答を提供することはできません。-1
jpmc26

2つのインターフェイスとそれらを実装する数百のクラスの場合、抽象化過度に引き伸ばしている可能性高いと私は主張します。私は確かに多くの場所で非常に曖昧な抽象化を再利用するプロジェクトでこれを見てきました(interface Doer {void prepare(); void doIt();})。答えの重要な部分は、抽象化を変更する必要がある場合にテストを適用することです。変更しない場合、痛みを引き起こすことはありません。
James_pic

24

デザインパターンの問題は、「ハンマーを持っていると、すべてが釘のように見える」ということわざに要約できます。設計パターンを適用する行為は、プログラムを改善するものではありません。実際、デザインパターンを追加する場合は、より複雑なプログラムを作成することになります。設計パターンをうまく利用しているかどうかは疑問のままであり、これが「いつ抽象化が過剰になるのか」という質問の核心です。

単一の実装用にインターフェースと抽象スーパークラスを作成する場合、余分で不要な2つのコンポーネントをプロジェクトに追加しました。インターフェイスを提供することのポイントは、それがどのように機能するかを知らずにプログラム全体で平等に処理できるようにすることです。抽象スーパークラスのポイントは、実装の基本的な動作を提供することです。実装が1つしかない場合、すべての複雑なインターフェイスと抽象クラスが提供しますが、利点はありません

同様に、Factoryパターンを使用していて、スーパークラスでのみ使用可能な機能を使用するためにクラスをキャストしている場合、Factoryパターンはコードに利点を追加しません。回避できた可能性のある追加のクラスをプロジェクトに追加しただけです。

TL; DR抽象化の目的はそれ自体が抽象的ではないというのが私のポイントです。プログラムで非常に実用的な目的を果たします。デザインパターンを使用するか、インターフェイスを作成する前に、追加の複雑さやプログラムがより堅牢であるにもかかわらず、プログラムが理解しやすいかどうかを自問する必要があります。追加の複雑さ(できれば両方)にもかかわらず。答えが「いいえ」または「多分」の場合、数分かけて、なぜそれをしたいのかを検討し、おそらくコードに抽象化を追加する必要なく、代わりにもっと良い方法で行うことができるかどうかを検討してください。


ハンマーの例えは、1つの設計パターンしか知らないという問題です。設計パターンは、適切な場所から選択して適用するためのツールキット全体を作成する必要があります。ナットを割るためにハンマーを選択しません。
ピートKirkham

@PeteKirkham本当ですが、自由に使えるデザインパターンの配列全体でさえ、特定の問題に適切に適合しない場合があります。スレッジハンマーがナットを割るのに最適でなく、ドライバーでもなければ、ハンマーがないために巻尺でもない場合、それはスレッジハンマーを仕事に適したピックにしません、それはちょうど作りますそれはあなたの処分で最も適切なツールです。だからといって、ハンマーを使ってナッツを割る必要があるというわけではありません。正直、率直に言って、本当に必要なのはハンマーではなくくるみ割り人形です
Neil

ナッツの割れに訓練されたリスの軍隊が欲しい。
icc97

6

TL:DR;

私は、「必要な」数のアブレーションのレベルがあり、それより下では少なすぎる、または上では多すぎるとは思わない。グラフィックデザインの場合と同様に、優れたOOPデザインは目に見えないものであり、当然のことと考えてください。悪いデザインはいつも痛い親指のように突き出ています。

長い答え

おそらく、あなたが構築している抽象化のレベルの数を知ることは決してないでしょう。

ほとんどのレベルの抽象化は私たちには見えないので、当然のことと考えています。

この推論により、この結論に至ります。

抽象化の主な目的の1つは、システムのすべての動作を常に念頭に置く必要性をプログラマーに保存することです。設計により、何かを追加するためにシステムについて多くのことを知る必要がある場合、おそらく抽象化が少なすぎます。悪い抽象化(貧弱なデザイン、貧弱なデザイン、または過剰なエンジニアリング)も、何かを追加するためにあなたにあまりにも多くの知識を強いることができると思います。1つの極端な例では、神のクラスまたはDTOの束に基づいたデザインがあり、もう1つの極端な例では、数え切れないほどのフープを飛び越えてハローワールドを実現するOR /永続フレームワークがあります。どちらの場合も、あなたはあまりにも多くのことを知りすぎます。

悪い抽象化はガウスの鐘に固執します。というのは、一度あなたがスイートスポットを通り過ぎると邪魔になり始めるからです。一方、優れた抽象化は目に見えず、そこにあることに気付かないため、あまり抽象化することはできません。APIのレイヤー、ネットワークプロトコル、ライブラリ、OSライブラリ、ファイルシステム、ハーウェアレイヤーなど、アプリケーションの基盤となるレイヤーの数を考えてください。

抽象化のその他の主な目的は区画化であるため、エラーは特定の領域を超えて浸透しません。二重船体とは別に、船体の一部に穴がある場合に船が完全に浸水するのを防ぎます。コードへの変更によって、一見無関係な領域にバグが作成される場合、抽象化が少なすぎる可能性があります。


2
「ほとんどの場合、構築している抽象化のレベルの数を知ることは決してないでしょう。」–実際、抽象化の全体的なポイントは、それがどのように実装されているのかわからない、IOWはそれが何レベルの抽象化を隠すのかを知らない(できない)ことです。
ヨルグWミッターク

4

設計パターンは、単に問題の一般的な解決策です。デザインパターンを知ることは重要ですが、それらは原因ではなく、適切にデザインされたコードの症状にすぎません(優れたコードは、4つのデザインパターンのギャングから抜け出すことができます)。

抽象化はフェンスのようなものです。これらは、プログラムの領域をテスト可能なチャンクと交換可能なチャンクに分割するのに役立ちます(非脆弱で非剛性のコードを作成するための要件)。そして、フェンスのように:

  • サイズを最小化するために、自然なインターフェースポイントで抽象化が必要です。

  • それらを変更したくありません。

  • 独立させることができるものを分離したい。

  • 間違った場所に持っていることは、持っていないことよりも悪いことです。

  • 大きな漏れがあってはいけません。


4

リファクタリング

今まで一度も言及された「リファクタリング」という言葉は見ませんでした。それで、ここに行きます:

新しい機能をできるだけ直接実装してください。単一の単純なクラスしかない場合、インターフェイス、スーパークラス、ファクトリなどは必要ありません。

太りすぎてクラスを拡張していることに気づいたら、それを引き裂くときです。その時点で、実際にどのように行うべきを考えることは非常に理にかなっています。

パターンは心のツールです

パターン、より具体的には4人のギャングによる「Design Patterns」という本は、とりわけ、開発者が考え、話し合うための言語を構築しているため、素晴らしいものです。 「ファサード」と誰もがすぐにそれが何を意味するかを正確に知っています。

私の意見では、すべての開発者は、少なくとも基本を説明することなくオブジェクト指向の概念について話せるようにするために、少なくとも元の本のパターンに関する十分な知識を持っている必要があります。実際にパターンを使用する可能性が現れるたびに、実際にパターンを使用する必要がありますか?ほとんどありません。

図書館

ライブラリは、パターンベースの選択肢が少なすぎるのではなく、多すぎるためにエラーになる可能性がある1つの領域です。「脂肪」クラスから、よりパターンに由来するもの(通常、より多くの小さなクラスを意味する)に変更すると、インターフェイスが根本的に変更されます。それはあなたのライブラリのユーザーにとって本当に興味のある唯一のものであるため、それはあなたがライブラリで通常変更したくない一つのことです。内部的に機能を扱う方法についてはあまり気にしませんが、新しいAPIで新しいリリースを行うときにプログラムを絶えず変更しなければならない場合、彼らは非常に気にかけます。


2

抽象化のポイントは、何よりもまず、抽象化の利用者、つまり抽象化のクライアント、他のプログラマー、そしてしばしば自分自身にもたらされる価値であるべきです。

抽象化を使用しているクライアントとして、プログラミング作業を完了するために多くの異なる抽象化を組み合わせて一致させる必要がある場合、潜在的に多すぎる抽象化があります。

理想的には、階層化により多くの下位の抽象化をまとめ、その下位の抽象化を処理せずに消費者が使用できる単純で高レベルの抽象化に置き換える必要があります。基礎となる抽象化を処理する必要がある場合、レイヤーはリークしています(不完全であるため)。消費者があまりにも多くの異なる抽象化に対処しなければならない場合、おそらく階層化が欠落しています。

消費するプログラマの抽象化の価値を検討した後、DRY-nessのような実装の評価と検討に移ることができます。

はい、メンテナンスを簡単にすることですが、消費者のメンテナンスのmaintenance状をまず考慮し、品質の抽象化とレイヤーを提供してから、冗長性を回避するなどの実装面で独自のメンテナンスを緩和することを検討する必要があります。


例、Memcacheを使用して汎用キャッシングレイヤーを作成する場合。Memcache、MemcacheAdapter、MemcacheInterface、AbstractCache、CacheFactory、CacheConnectorなどが本当に必要なのでしょうか?または、これらのクラスの半分だけを使用する場合、これは保守が容易であり、まだ良いコードですか?

クライアントの視点を見なければなりません。彼らの生活が楽になれば、それは良いことです。彼らの生活がより複雑な場合、それは悪いです。ただし、これらの要素を使いやすいものにまとめるレイヤーが欠落している可能性があります。内部的には、これらは実装のメンテナンスを改善する可能性が非常に高くなります。ただし、あなたが疑うように、単に過剰に設計されている可能性もあります。


2

抽象化は、コードを理解しやすくするために設計されています。抽象化の層が物事をより混乱させようとしているなら、それをしないでください。

目的は、正しい数の抽象化とインターフェースを使用して以下を行うことです。

  • 開発時間を最小限に抑える
  • コードの保守性を最大化する

必要な場合にのみ要約

  1. スーパークラスを書いていることに気付いたら
  2. 重要なコードの再利用が許可される場合
  3. 抽象化によりコードが大幅に明確になり読みやすくなる場合

抽象化しない場合

  1. これを行うと、コードの再利用や明確性に利点がなくなります
  2. そうすると、コードが大幅に長く/複雑になり、メリットはありません

いくつかの例

  • プログラム全体で1つのキャッシュのみを使用する場合は、スーパークラスになる可能性があると思われる場合を除き、抽象化しないでください。
  • 3つの異なるタイプのバッファーがある場合、それらすべてに共通のインターフェース抽象化を使用します

2

これは議論の余地のあるメタアンサーかもしれないと思うし、私はパーティーに少し遅れているが、ここでこれを言及することは非常に重要だと思う。

デザインパターンの使用方法に関する問題は、デザインパターンが教えられると、次のようなケースを提示することです。

この特定のシナリオがあります。このようにコードを整理します。これはスマートに見えるが、やや不自然な例です。

問題は、実際のエンジニアリングを始めたとき、物事はそれほどカットアンドドライではないということです。読んだ設計パターンは、解決しようとしている問題に完全には適合しません。言うまでもなく、あなたが使用しているライブラリは、それらのパターンを説明する本文に記載されているすべてに違反しています。そしてその結果、あなたが書くコードは「間違っている」と感じ、このような質問をします。

これに加えて、ソフトウェアエンジニアリングについて話すとき、Andrei Alexandrescuを引用したいと思います。

ソフトウェアエンジニアリングは、他のエンジニアリング分野よりも多分、豊富な多様性を示しています。同じことを非常に多くの正しい方法で行うことができ、善悪には無限のニュアンスがあります。

おそらくこれは少し誇張されているかもしれませんが、これはあなたがあなたのコードに自信がないと感じるかもしれない追加の理由を完璧に説明していると思います。

InsomniacのゲームエンジンリーダーであるMike Actonの予言的な声が頭の中で叫ぶのは、このような時代です。

データを知る

彼はあなたのプログラムへの入力と、望ましい出力について話しています。そして、Mythical Man Monthからのこのフレッド・ブルックスの宝石があります:

フローチャートを見せてテーブルを隠してください。テーブルを見せてください。通常、フローチャートは必要ありません。それらは明らかです。

もし私があなただったら、典型的な入力ケースに基づいて問題について推論し、それが望ましい正しい出力を達成するかどうかを判断します。そして、このような質問をしてください:

  • プログラムからの出力データは正しいですか?
  • 最も一般的な入力ケースに対して効率的/迅速に生成されますか?
  • 私と私のチームメイトの両方にとって、私のコードはローカルで推論するのに十分簡単ですか?そうでない場合は、それをもっと簡単にできますか?

これを行うと、「抽象化または設計パターンの層がいくつ必要か」という質問の答えがはるかに簡単になります。何層の抽象化が必要ですか?これらの目標を達成するために必要なだけ、そしてそれ以上。「デザインパターンはどうですか?使用したことがありません!」さて、パターンを直接適用せずに上記の目標を達成した場合、それは問題ありません。動作させて、次の問題に進みます。コードからではなく、データから始めます。


2

ソフトウェアアーキテクチャが言語を発明している

すべてのソフトウェアレイヤーで、あなた(または同僚)が次の上位レイヤーソリューションを表現したい言語を作成します(したがって、私の投稿では自然言語の類似物をいくつか紹介します)。ユーザーは、その言語の読み方や書き方の学習に何年も費やしたくありません。

このビューは、アーキテクチャの問題を決定するときに役立ちます。

読みやすさ

その言語は簡単に理解できるはずです(次の層のコードを読みやすくします)。コードは、書かれているよりもはるかに頻繁に読み取られます。

1つの概念を1つの単語で表現する必要があります。1つのクラスまたはインターフェイスで概念を公開する必要があります。(スラヴ語は通常、1つの英語の動詞に対して2つの異なる単語を持っているため、語彙を2倍習得する必要があります。すべての自然言語は、複数の概念に対して1つの単語を使用します)。

公開する概念に驚きが含まれてはなりません。これは主にgetメソッドやsetメソッドなどの命名規則です。また、デザインパターンは標準的なソリューションパターンを提供するので役立ちます。しかし、単純に具体的なクラスをインスタンス化するだけで十分であれば、私はそれを好むでしょう。

使いやすさ

言語は使いやすいものでなければなりません(「正しい文章」を簡単に作成できるようにする)。

これらすべてのMemCacheクラス/インターフェースが次のレイヤーに表示されるようになった場合、ユーザーは、キャッシュの単一の概念でこれらの単語をいつ、どこで使用するかを理解するまで、急な学習曲線を作成します。

必要なクラス/メソッドのみを公開すると、ユーザーが必要なものを見つけやすくなります(Antoine de Saint-ExuperyのDocBrownsの引用を参照)。実装クラスの代わりにインターフェースを公開すると、それが簡単になります。

確立されたデザインパターンを適用できる機能を公開する場合、異なるデザインを作成するよりも、そのデザインパターンに従う方が適切です。ユーザーは、まったく異なる概念よりもデザインパターンに従うAPIを簡単に理解できます(イタリア語を知っている場合、スペイン語は中国語よりも簡単です)。

概要

使用が簡単になる場合は抽象化を導入します(抽象化と実装の両方を維持するオーバーヘッドの価値があります)。

コードに(重要な)サブタスクがある場合、「予想される方法」で解決します。つまり、異なるタイプのホイールを再発明するのではなく、適切な設計パターンに従います。


1

考慮すべき重要なことは、ビジネスロジックを実際に処理する消費コードが、これらのキャッシュ関連クラスについてどれだけ知る必要があるかです。理想的には、コードは作成したいキャッシュオブジェクトと、コンストラクターメソッドが十分でない場合にそのオブジェクトを作成するファクトリーのみを考慮する必要があります。

使用されるパターンの数または継承のレベルは、各レベルを他の開発者に正当化できる限り、それほど重要ではありません。これにより、追加の各レベルを正当化するのが難しくなるため、非公式の制限が作成されます。より重要な部分は、機能要件またはビジネス要件の変更によって影響を受ける抽象化レベルの数です。単一の要件に対して1つのレベルのみに変更を加えることができる場合、抽象化または抽象化が不十分である可能性は低く、複数の無関係な変更に対して同じレベルを変更する場合は、抽象化されている可能性が高く、懸念をさらに分離する必要があります。


-1

まず、Twitterの引用は偽です。新しい開発者はモデルを作成する必要があります。通常、抽象化は「画像を取得する」のに役立ちます。もちろん、抽象化が意味をなすことを条件とします。

第二に、あなたの問題は抽象化が多すぎたり少なすぎたりしないので、明らかにこれらのことについて誰も決定することができないということです。誰もコードを所有しておらず、単一の計画/設計/哲学は実装されていません。次の人は、その瞬間に彼が適切と思われるものは何でもできます。どちらのスタイルを選択しても、それは1つでなければなりません。


2
「偽物」としての経験を却下することは控えましょう。抽象化が多すぎるのは本当の問題です。悲しいことに、実際の問題を解決するのではなく、「ベストプラクティス」であるため、人々は事前に抽象化を追加します。また、誰も「これらのことについて」決定することはできません...人々は会社を去り、人々は参加し、誰も泥の所有権を取りません。
-Rawkode
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.