web-dev-qa-db-ja.com

デザインパターンにそれほど多くのクラスが必要なのはなぜですか?

私はシニアの中でジュニア開発者であり、彼らの思考や推論を理解することに多くの苦労をしています。

私は Domain-Driven Design (DDD)を読んでいますが、なぜこれほど多くのクラスを作成する必要があるのか​​理解できません。ソフトウェアを設計するその方法に従うと、最大で2つのファイルと3〜4つの関数で置き換えることができる20〜30のクラスになります。はい、これは煩雑になる可能性がありますが、はるかに保守しやすく、読みやすくなっています。

ある種のEntityTransformationServiceImplが何をするのかを見たいときはいつでも、多くのクラス、インターフェース、それらの関数呼び出し、コンストラクター、それらの作成などを追跡する必要があります。

簡単な数学:

  • 60行のダミーコードvs 10クラスX 10(このようなロジックはまったく異なるとしましょう)= 600行の乱雑なコードvs 100クラス+ラップして管理するためのいくつか。依存性注入を追加することを忘れないでください。
  • 600行の乱雑なコードを読む= 1日
  • 100クラス= 1週間、いつでも誰が何をするかを忘れる

メンテナンスは簡単だと誰もが言っていますが、何のために?新しい機能を追加するたびに、ファクトリー、エンティティー、サービス、および値を持つ5つのクラスを追加します。この種のコードの移動は、乱雑なコードよりもはるかに遅いように感じます。

たとえば、1か月で50K LOCの乱雑なコードを書く場合、DDDには多くのレビューと変更が必要です(どちらの場合もテストはかまいません)。 1つの単純な追加は、それ以上ではなくても1週間かかります。

1年間で多くの乱雑なコードを記述し、それを何度も書き換えることさえできますが、DDDスタイルでは、乱雑なコードと競合するのに十分な機能がまだありません。

説明してください。なぜこのDDDスタイルと多くのパターンが必要なのですか?

UPD 1:すばらしい回答をたくさん受け取りました。コメントをどこかに追加するか、リストを読むためのリンクを使用して回答を編集してください(どちらからだかわかりません)はじめに、DDD、デザインパターン、UML、コードコンプリート、リファクタリング、実用的、...非常に多くの優れた書籍)、もちろんシーケンスを使用しているため、一部の人と同じように理解を始め、上級者になることができます。

216
user1318496

これは最適化の問題です

優れたエンジニアは 最適化の問題 がターゲットなしでは無意味であることを理解しています。単に最適化することはできません。何かを最適化する必要がありますfor。たとえば、コンパイラオプションには、速度の最適化とコードサイズの最適化が含まれます。これらは時々反対の目標です。

私は私のデスクが追加用に最適化されていることを妻に伝えたいです。それは単なる山であり、ものを追加するのは非常に簡単です。妻は、検索用に最適化した場合、つまり、ものを少し整理して検索できるようにした場合、それを好みます。これにより、もちろん追加が難しくなります。

ソフトウェアも同じです。あなたは確かに製品の作成のために最適化することができます-大量の モノリシックコード を生成することを心配することなく、可能な限り迅速に生成します。すでにお気づきのとおり、これは非常に高速です。代わりの方法は、メンテナンスを最適化することです。作成を少し難しくするだけでなく、変更をより簡単に、またはリスクを少なくします。それが構造化コードの目的です。

成功するソフトウェア製品は一度だけ作成され、何度も修正されることをお勧めします。経験豊富なエンジニアは、構造化されていないコードベースが独自の生命を帯びて製品になり、サイズと複雑さが増すまで、大きな変更を加えることなく大きな変更を加えることは非常に困難です。コードが構造化されていれば、リスクを抑えることができます。それが私たちがこのすべてのトラブルに行く理由です。

複雑さは要素ではなく関係に由来する

分析では、量(コードの量、クラスの数など)を調べていることに気づきました。これらは興味深いものですが、実際の影響は、要素間の関係から発生します。たとえば、10個の関数があり、どちらに依存するかわからない場合、90の可能な関係(依存関係)があり、心配する必要があります.10個の関数のそれぞれは、他の9個の関数のいずれかに依存している可能性があり、9 x 10 =90。どの関数がどの変数をどのように変更するか、データがどのように渡されるかわからない可能性があるため、特定の問題を解決するときに、コーダーは心配すべきことがたくさんあります。対照的に、30のクラスがあり、それらが巧妙に配置されている場合、29のリレーションを持つことができます。レイヤー化されている場合、またはスタックに配置されている場合。

これはチームのスループットにどのように影響しますか?まあ、依存関係は少なく、問題ははるかに扱いやすいです。コーダーは、変更を加えるたびに頭の中で何十億ものことを処理する必要はありません。したがって、依存関係を最小限に抑えることは、問題について適切に推論する能力を大幅に高めることができます。そのため、物事をクラスまたはモジュールに分割し、変数をできるだけ厳密にスコープし、 [〜#〜] solid [〜#〜] 原則を使用します。

338
John Wu

まあ、まず第一に、可読性と保守性は多くの場合、見る人の目にあります。

あなたが読むことができるものはあなたの隣人にはないかもしれません。

保守性は、多くの場合、発見可能性(コードベースで動作または概念を発見するのがいかに簡単か)に要約され、発見可能性は別の主観的なものです。

DDD

DDDが開発者のチームを支援する方法の1つは、コードの概念と動作を編成する特定の(まだ主観的)方法を提案することです。これはconventionにより、物事を発見しやすくなるため、アプリケーションの保守が容易になります。

  • ドメインの概念はエンティティおよび集約としてエンコードされます
  • ドメインの動作はエンティティまたはドメインサービスにあります
  • 一貫性は集約ルートによって保証されます
  • 永続性の懸念はリポジトリによって処理されます

この配置は客観的に維持するのが容易ではありません。ただし、DDDコンテキストで操作していることを誰もが理解していると、ある程度可能維持が容易になります。

クラス

クラスはよく知られているconventionであるため、保守性、可読性、発見性などに役立ちます。

オブジェクト指向の設定では、クラスは通常、密接に関連する動作をグループ化し、慎重に制御する必要がある状態をカプセル化するために使用されます。

それは非常に抽象的なように聞こえますが、次のように考えることができます。

クラスを使用すると、クラス内のコードがどのように機能するか /を知る必要はありません。 何がクラスが責任を持っているかを知る必要があるだけです。

クラスを使用すると、十分に定義されたコンポーネントの間で相互作用の観点からアプリケーションを推論できます。

これにより、アプリケーションの動作を推論する際の認知的負担が軽減されます。 600行のコードが何を実行するかを覚える必要はなく、 30個のコンポーネントがどのように相互作用するかを考えることができます。

そして、これらの30個のコンポーネントがアプリケーションの 3レイヤーにまたがっていることを考えると、おそらく一度におよそ10個のコンポーネントを推論する必要があるだけです。

それはかなり扱いやすいようです。

概要

基本的に、上級開発者が行っていることは次のとおりです。

それらはアプリケーションを easy about クラスに分解します。

その後、彼らはこれらをに整理し、約レイヤーを推論しやすくします。

彼らがこれをしているのは、アプリケーションが成長するにつれて、全体としてそれを推論することがますます難しくなることを知っているからです。レイヤーとクラスに分解すると、アプリケーション全体について論理的に考える必要がなくなります。彼らは、その小さなサブセットについて推論する必要があるだけです。

67
MetaFight

私に説明してください、なぜこのDDDスタイル、たくさんのパターンが必要なのですか?

まず、メモ:DDDの重要な部分はパターンではなく、開発作業とビジネスの整合です。グレッグ・ヤングは、ブルーブックの章は 間違った順序 にあると述べました。

ただし、特定の質問に対しては、予想よりもはるかに多くのクラスが存在する傾向があります。(a)ドメインの動作と配管を区別するための努力が行われているため、および(b)その概念を確実にするために追加の努力が行われているためドメインモデルでは明示的に表現されます。

はっきり言って、ドメインに2つの異なる概念がある場合、たとえメモリ表現で同じものを共有していても、モデル内でそれらを区別する必要があります

実際には、ドメインの専門家がモデルを見てエラーを見つけることができるように、モデルをビジネスの言語で記述するドメイン固有の言語を構築しています。

さらに、懸念の分離にもう少し注意が払われていることがわかります。そして、実装の詳細からある能力の消費者を隔離するという概念。 D。L. Parnas を参照してください。明確な境界により、ソリューション全体に波及する影響なしに実装を変更または拡張できます。

ここでの動機:ビジネスのコアコンピテンシーの一部であるアプリケーション(競争上の優位性を生み出す場所を意味する)の場合、ドメインの動作をより簡単に、より安価に、より良いバリエーションに置き換えることができるようにする必要があります。実際には、プログラムの一部を急速に進化させたい(状態が時間とともにどのように進化する)か、他の部分をゆっくりと変化させたい(状態を格納する方法)かを決定します。抽象化の追加の層は、誤って一方を他方に結合することを回避するのに役立ちます。

公平に言えば、その一部はオブジェクト指向の脳損傷でもあります。 Evansによって最初に記述されたパターンは、15年以上前に彼が参加したJavaプロジェクトに基づいています。状態と動作はそのスタイルで密接に結合されているため、回避した方がよい複雑化につながります。 Perception and Action by Stuart Halloway、または Inlining Code by John Carmack。

使用する言語に関係なく、関数型のプログラミングは利点をもたらします。都合のいいときはいつでも、都合の悪いときは一生懸命考えるべきです。 Carmack、2012

29
VoiceOfUnreason

他の答えには多くの良い点がありますが、私はそれらがあなたがする重要な概念上の間違いを見逃したり、強調したりしないと思います:

完全なプログラムを理解するための労力を比較しています。

これは、ほとんどのプログラムで現実的なタスクではありません。単純なプログラムでさえ、非常に多くのコードで構成されているため、一度にすべてを頭の中で管理することは不可能です。あなたの唯一のチャンスは、目前のタスク(バグの修正、新しい機能の実装)に関連するプログラムの部分を見つけて、それで作業することです。

プログラムが巨大な関数/メソッド/クラスで構成されている場合、これはほとんど不可能です。このコードのチャンクが問題に関連しているかどうかを判断するには、何百行ものコードを理解する必要があります。あなたが与えた見積もりを使用すると、作業する必要のあるコードを見つけるためだけに1週間費やすことが容易になります。

これを、特定のロジックの検索/配置場所を明確にするパッケージ/名前空間に名前が付けられ、編成された小さな関数/メソッド/クラスのコードベースと比較してください。多くの場合、正しく実行すると、正しい場所にジャンプして問題を解決できます。少なくとも、デバッガーを起動すると、いくつかのホップで正しい場所に移動できる場所にジャンプできます。

私は両方の種類のシステムで働いてきました。違いは、同等のタスクと同等のシステムサイズのパフォーマンスで2桁の大きさになる可能性があります。

これが他の活動に及ぼす影響:

  • ユニットが小さいとテストがはるかに簡単になります
  • 2人の開発者が同じコードで作業する機会が少ないため、マージの競合が少なくなります。
  • 断片を再利用する(そしてそもそも断片を見つける)のが簡単なので、重複が少なくなります。
29
Jens Schauder

コードのテストはコードの記述よりも難しいため

多くの答えは、開発者の観点からの適切な推論を与えました-そのメンテナンスを減らすことができますが、最初にコードを書くのにもっと労力を費やすことになります。

ただし、考慮すべきもう1つの側面があります。テストは、元のコードと同じようにきめ細かく行うことができます。

すべてをモノリスで書く場合、書くことができる唯一の効果的なテストは、「これらの入力が与えられた場合、出力は正しいですか?」です。つまり、見つかったバグは「巨大なコードの山のどこかに」スコープされます。

もちろん、開発者にデバッガーを用意して、問題の発生場所を正確に見つけ、修正に取り組むこともできます。これは多くのリソースを必要とし、開発者の時間の悪用です。マイナーなバグがあるとしたら、開発者はプログラム全体を再度デバッグする必要があります。

解決策:1つの特定の潜在的な失敗をそれぞれ特定する多くの小さなテスト。

これらの小さなテスト(たとえば、単体テスト)には、コードベースの特定の領域をチェックし、限られた範囲内でエラーを見つけるのに役立つという利点があります。これは、テストが失敗したときのデバッグをスピードアップするだけでなく、すべての小さなテストが失敗した場合に、より大きなテストで失敗をより簡単に見つけることができることを意味します(つまり、特定のテストされた関数にない場合は、相互作用にある必要がありますそれらの間の)。

明らかなように、テストを小さくするには、コードベースをテスト可能な小さなチャンクに分割する必要があります。これを行う方法は、大規模な商用コードベースで、作業中のコードのようになることがよくあります。

補足として:これは、人々が物事を「遠すぎ」て取らないと言っているのではありません。しかし、コードベースをより小さな/より接続されていない部分に分離する正当な理由があります-賢明に行われた場合

19
Bilkokuya

説明してください、なぜこのDDDスタイル、たくさんのパターンが必要なのですか?

私たちの多く(ほとんど...)は本当に必要しません。理論家と非常に高度で経験豊富なプログラマーは、多くの研究とその深い経験の結果として、理論と方法論についての本を書きます。

ジュニアデベロッパーとしては、先ほど述べたような本を読んで視野を広げ、特定の問題に気付くのはよいことです。また、上級の同僚が慣れていない用語を使用したときに、恥ずかしくて慌てないようにすることができます。何か非常に難しいことを見つけて、意味をなさない、または役に立たないと思われる場合は、それについて自殺しないでください。そのような概念やアプローチがあることを頭に入れてください。

日々の開発において、あなたが学者でない限り、あなたの仕事は、実行可能で保守可能な解決策を見つけることです。本で見つけたアイデアがあなたがその目標を達成するのに役立たない場合でも、あなたの仕事が満足のいくものであると思われる限り、それについて今心配する必要はありません。

読んだことの一部は使用できるが、最初は完全に「取得」できなかった、またはできないかもしれないことに気付く時が来るかもしれません。

17
Vector

あなたの質問は多くの根拠をカバーしており、多くの仮定があるので、私はあなたの質問の主題を選び出します:

なぜデザインパターンにそれほど多くのクラスが必要なのですか

私たちはしない。デザインパターンには多くのクラスが必要であるという一般的に受け入れられているルールはありません。

コードを配置する場所と、タスクを異なるコード単位に分割する方法を決定するための2つの重要なガイドがあります。

  • 結束:コードの任意の単位(パッケージ、ファイル、クラス、メソッドなど)は一緒に属するである必要があります。つまり、特定のメソッドにはoneタスクが必要であり、それをうまく実行する必要があります。すべてのクラスがoneより大きなトピック(何であれ)を担当する必要があります。高い結束力が欲しい。
  • カップリング:コードの2つのユニットは、互いにほとんど依存しないようにしてください。循環依存関係は特にないはずです。低いカップリングが必要です。

なぜこれら2つが重要なのでしょうか。

  • 結束:多くのことを行うメソッド(たとえば、GUI、ロジック、DBアクセスなどをすべて1つの長いコードの混乱で実行する旧式のCGIスクリプト)は扱いにくくなります。執筆の時点では、あなたの考えの列を長い方法に単に入れたくなります。これは機能し、プレゼンテーションなどが簡単で、それで完了できます。後で問題が発生します。数か月後、あなたは自分のしたことを忘れてしまうかもしれません。上部のコード行は、下部の行から数画面離れている場合があります。すべての詳細を忘れがちです。メソッドのどこかを変更すると、複雑なものの動作がいくらか壊れることがあります。メソッドの一部をリファクタリングまたは再利用することは非常に面倒です。等々。
  • カップリング:コードのユニットを変更すると、それに依存する他のすべてのユニットが壊れる可能性があります。 Javaのような厳密な言語では、コンパイル時にヒント(つまり、パラメーター、宣言された例外など)が表示される場合があります。しかし、多くの変更はそのようなことを引き起こしていません(つまり、動作の変更)。他のより動的な言語には、そのような可能性はありません。カップリングが高ければ高いほど、何かを変更するのが難しくなり、ある目標を達成するために完全な再書き込みが必要な場合、Grindが停止する可能性があります。

これらの2つの側面は、任意のプログラミング言語の「どこに何を置くか」を選択するための基本的な「ドライバー」、および任意のパラダイム(オブジェクト指向だけでなく)です。誰もが明示的にそれらに気づいているわけではなく、これらがソフトウェアにどのように影響するかについて本当に染み込んだ、自動的な感覚を得るには、多くの場合数年かかります。

明らかに、これら2つの概念は実際に何をすべきかについては何も教えてくれませんdo。一部の人々は多すぎる側に誤りを犯し、他の人は少なすぎる側に誤りを犯します。一部の言語(ここではJavaを見てください)は、言語自体の非常に静的で知識の豊富な性質のため、多くのクラスを支持する傾向があります(これは価値のあるステートメントではありませんが、それはそれです)。これは、Rubyなどの動的でより表現力のある言語と比較すると特に顕著になります。

別の側面は、一部の人々は、必要なコードのみを書くというアジャイルなアプローチに同意している今すぐであり、必要に応じて後で大幅にリファクタリングすることです。この開発スタイルでは、実装クラスが1つしかない場合はinterfaceを作成しません。具象クラスを実装するだけです。後で2番目のクラスが必要になった場合は、リファクタリングします。

一部の人々は単にそのように働きません。それらは、これまでより一般的に使用できるあらゆるもののためのインターフェース(または、より一般的には、抽象基本クラス)を作成します。これにより、クラスの爆発がすぐに起こります。

繰り返しになりますが、賛否両論あり、私もあなたもどちらを好むかは問題ではありません。あなたは、ソフトウェア開発者としての生活の中で、長いスパゲッティメソッドから、賢明で非常に十分なクラス設計から、非常に過剰に設計された信じられないほど爆破されたクラススキーマまで、あらゆる極端に遭遇します。経験を重ねるにつれて、「建築」の役割に成長し、望む方向に影響を与えることができます。あなたは自分のための黄金の真ん中を見つけます、そして、あなたはあなたが何をしても、多くの人々があなたに反対するでしょう。

したがって、ここでは、オープンマインドを保つことが最も重要です。これが私の第一のアドバイスです。残りの質問から判断すると、問題に非常に苦しんでいるように見えます...

8
AnoE

経験豊富なコーダーは学んだ:

  • それらの小さめのプログラムと口論は、特に成功したもののように成長し始めるからです。単純なレベルで機能するその単純なパターンはスケーリングしません。
  • 追加/変更ごとにいくつかのアーティファクトを追加/変更するのは面倒なように思えるかもしれませんが、何を追加するかはわかっていて、簡単に追加できます。 3分間のタイピングは、3時間の巧妙なコーディングに勝ります。
  • オーバーヘッドが実際に良いアイデアである理由が理解できないからといって、多くの小さなクラスは「厄介」ではありません。
  • ドメインの知識が追加されていないと、「私にとって明白な」コードは、チームメイトにとってしばしば神秘的です...加えて、未来の私。
  • 部族の知識があると、プロジェクトをチームメンバーに簡単に追加するのが非常に難しくなり、プロジェクトを迅速に生産的にすることができなくなります。
  • ネーミングは、コンピューティングにおける2つの困難な問題の1つであり、多くのクラスとメソッドは、多くの場合、非常に有益であることがわかっているネーミングにおける激しい練習です。
7
Michael Durrant

これまでの答えは、すべて良いものであり、質問者が何かを欠落しているという合理的な仮定から始まりました。質問者が基本的に正しいことも可能であり、この状況がどのようにして起こり得るかについて議論する価値があります。

経験と実践は強力であり、物事を管理する唯一の方法がたくさんのEntityTransformationServiceImplを使用することである大規模で複雑なプロジェクトで経験を積んだ場合、彼らはデザインパターンと緊密な遵守に迅速かつ快適になりますDDDに。小規模なプログラムであっても、軽量のアプローチを使用した場合は効果がはるかに低くなります。奇妙なことに、あなたは適応するべきであり、それは素晴らしい学習経験になるでしょう。

ただし、適応しながら、単一のアプローチを深く学ぶことから、どこにでも機能させることができるまでのバランスを取ることと、多用途性を維持し、どのツールにも精通していなくても利用可能なツールを理解することのバランスをとることをレッスンとして取り入れてください。両方に利点があり、両方とも世界で必要とされています。

2
Josh Rumbut

この回答の最初の投稿では期待どおりの結果が得られなかったので、この本のfor-Kindleバージョンを購入し、質問に直接回答するために、漠然と覚えていたものを正確に見つけました

デザインパターンにそれほど多くのクラスが必要なのはなぜですか?

短くて甘い-私たちはしません。なぜそれが本当なのかというと、この本だけでなく言葉にもできるかどうかわかりません。それが、最初に投稿した回答の要点でした。

次に続くのは、質問によく話す本の抜粋です。

本からのこの抜粋は、第3章「パターン」および第3章「パターンを実装する多くの方法があります」からです。

この本では、デザインパターンの構造図とはかなり異なるパターン実装を見つけます。たとえば、複合パターンの構造図は次のとおりです。

Structure diagram for the Composite pattern

そして、ここに複合パターンの特定の実装があります:

A minimalistic implementation of the Composite pattern

ご覧のとおり、このCompositeの実装は、Compositeの構造図とほとんど似ていません。これは、必要なものだけをコーディングした結果として生まれた、最小限の複合的な実装です。

パターンの実装を最小限にすることは、進化的設計の実践の一部です。多くの場合、非パターンベースの実装は、パターンを含めるために進化させる必要がある場合があります。その場合、設計を単純なパターン実装にリファクタリングできます。この本では、このアプローチを使用しています。

副章の少し前に、ジョシュアはこう言います:

この図は、CreatorクラスとProductクラスが抽象的であり、ConcreteCreatorクラスとConcreteProductクラスが具体的であることを示しています。これがFactoryメソッドパターンを実装する唯一の方法ですか?決して!実際、デザインパターンの作成者は、実装ノートと呼ばれるセクションで各パターンを実装するさまざまな方法を説明するために大変な苦労をしています。ファクトリメソッドパターンの実装に関する注意事項を読むと、ファクトリメソッドを実装する方法はたくさんあることがわかります。

これがそのセクション全体です。少し詳しく説明します。

Page screen shot from the Joshua Kerievsky book "Refactoring to Patterns" Kindle Edition, 13%, Location 1134 of 9053


【オリジナルポスト】

この作者はデザインパターンを実装することが多いので、デザインパターンで「非常に多くのクラスを使用する」ことが必要で、それがやり過ぎである場合のヒューリスティックな感覚で、あなたの質問に答えると思います。非常に質素で、少数のオブジェクト指向の「パーツ」があります。

enter image description here

この本は、Martin Fowler Signature Seriesの1つであり、Joshua Kerievskyによるデザインパターンに関する優れた本で、タイトルは "Refactoring To Patterns" ですが、本当のところは、 "Right-Factoring"とまたはパターンから離れて。それは私には値段に見合う価値がありました。

彼の本では、オブジェクト指向を使用せずにパターンを実装することもあり、代わりにいくつかのフィールドといくつかのメソッドを使用しました。そして、彼は時々それがやり過ぎだったので、完全にパターンを削除しました。それで、パターン化するときと、軽量化するときの「バランス」が読者への贈り物だったので、私は彼の主題の扱いを愛していました。

以下は、目次の最も関連性の高い部分のスクリーンショットです。それは私の最高の購入の1つでした。

Table of Contents Screenshot of Book Refactoring To Patterns by Joshua Kerievsky

この目次の中で、私は特に以下を強調します。

  • そして私は以下を強調します:
  • オーバーエンジニアリング
  • パターン万能薬
  • アンダーエンジニアリング
  • 人間が読めるコード
  • 清潔に保つ
  • 幸せなパターン
  • パターンを実装する方法はたくさんあります
  • パターンへの、パターンへの、そしてパターンからのリファクタリング
  • パターンはコードをより複雑にしますか?
  • コードのにおい

ですから、あなたの質問は、「パターンを実装するために本当に多くのクラスを使わなければならないのですか?」という質問に対する内部的な感覚として言い換えることができると思います。そして答えは「時々」であり、そしてもちろん、一見常に存在するように見える「依存する」。しかし、答えるのにもっと本が必要なので、できればこれを入手することをお勧めします。 (現時点では実際には持っていません。それ以外の場合は、良い例を紹介します-すみません。)HTH。

1