私は先輩の中で後輩の開発者ですが、先輩の考え方や理屈を理解するのにとても苦労しています。
ドメイン駆動設計]1(DDD)を読んでいるのですが、なぜこんなに多くのクラスを作る必要があるのか理解できないのです。この方法でソフトウェアを設計すると、20~30個のクラスができてしまいますが、これはせいぜい2つのファイルと3~4個の関数で置き換えることができます。確かに、これは面倒かもしれませんが、保守性と可読性は格段に向上します。
ある種の EntityTransformationServiceImpl
が何をするのか見たいときはいつでも、たくさんのクラス、インターフェース、それらの関数呼び出し、コンストラクタ、それらの生成などを追う必要があります。
単純な計算です。
誰もがメンテナンスが簡単だと言っていますが、何のためでしょうか?新しい機能を追加するたびに、ファクトリー、エンティティ、サービス、値などのクラスが5つ追加されます。この手のコードは、ごちゃごちゃしたコードよりもずっと動きが遅い気がするんです。
例えば、1ヶ月で50K LOCの汚いコードを書いたとして、DDDというのはたくさんのレビューと変更が必要です(私はどちらの場合もテストは気にしません)。単純な追加であれば、1週間以上かかることもあります。
1年後には、たくさんの雑なコードを書き、何度も書き換えることもできますが、DDDスタイルでは、雑なコードに対抗できるほどの機能はまだありません。
説明してください。なぜ、このDDDスタイルとたくさんのパターンが必要なのでしょうか?
UPD 1:たくさんの素晴らしい回答をいただきましたが、読書リスト(DDD、デザインパターン、UML、Code Complete、Refactoring、Pragmaticなど、たくさんの良書があってどれから読んだらいいかわからない)のリンクを、どこかにコメントを追加するか、回答を編集していただけませんか?
優秀なエンジニアは、「最適化問題」1は目標がなければ意味がないことを理解しています。ただ最適化するだけではなく、何かのために最適化しなければならないのです。例えば、コンパイラのオプションには、スピードの最適化とコードサイズの最適化がありますが、これらは時に正反対の目標になります。
私は妻に、「私の机は足し算に最適化されている」と言うのが好きです。それはただの山で、ものを追加するのはとても簡単なんです。妻は、私が検索に最適化されている、つまり、私がものを見つけられるように少し整理されていることを好むでしょう。そうすれば、もちろん、追加するのは難しくなります。
ソフトウェアも同じです。整理することを気にせずに、大量のモノリシックコードをできるだけ早く生成することです。すでにお気づきのように、これはとてもとても速いスピードでできます。もう一つの方法は、メンテナンスのために最適化することです。作成は少し難しくなりますが、変更はより簡単に、あるいはより少なくすることができます。これが構造化コードの目的です。
成功するソフトウェアというのは、一度作ったものを何度も何度も修正するものだと思うんです。経験豊富なエンジニアは、構造化されていないコードベースが独自の生命を持ち、製品となってサイズと複雑さを増し、小さな変更でさえ巨大なリスクを導入せずに行うことが非常に難しくなるのを見たことがあります。もしコードが構造化されていれば、リスクを抑制することができます。そのために、私たちはこのような苦労をしているのです。
あなたの分析では、コードの量やクラスの数など、量に着目しているようですね。これらはある意味興味深いのですが、本当の影響は要素間の関係から来るもので、それは組合せ的に爆発的に増加します。例えば、10個の関数があり、どれがどれに依存しているのか分からない場合、90個の関係(依存関係)を心配しなければなりません。10個の関数はそれぞれ他の9個の関数のどれかに依存しているかもしれず、9 x 10 = 90となります。どの関数がどの変数を変更し、データがどのように受け渡されるのか、全く分からないかもしれません。従って、コーダーは特定の問題を解決する際に心配することが山ほどあるのです。一方、30個のクラスがあっても、それらが巧妙に配置されていれば、例えばレイヤー化されていたり、スタック状に配置されていたりすれば、29個という少ない数の関係しか持たないこともある。
このことは、チームのスループットにどのような影響を与えるのでしょうか?そうですね、依存関係が少なければ、問題はずっと扱いやすくなります。コーダーは変更を加えるたびに、頭の中で何十億ものことをやりくりする必要がなくなります。つまり、依存関係を最小化することは、問題を適切に推論する能力を大きく向上させるのです。そのため、クラスやモジュールに分割したり、変数のスコープをできるだけ狭くしたり、「SOLID」3の原則を用いたりするのです。
まず、読みやすさと保守性は、見る人の目を奪うことが多いですね。
あなたにとって読みやすいものでも、隣の人にとってそうでない場合もあります。
メンテナンス性は、発見性(コードベース内でいかに簡単に動作やコンセプトを発見できるか)に帰結することが多く、発見性はまた主観的なものです。
DDDが開発者チームを支援する方法の1つは、コードの概念と動作を組織化する具体的な(まだ主観的な)方法を提案することです。 この慣習は、物事を発見することを容易にし、したがって、アプリケーションを維持することを容易にします。
この配置は、客観的には保守が容易であるとは言えません。 しかし、誰もがDDDの文脈で動いていることを理解すれば、維持することは計り知れないほど簡単です。
クラスは、よく知られた慣例であるため、保守性、可読性、発見性などの面で役に立ちます。
オブジェクト指向の環境では、クラスは通常、密接に関連する動作をグループ化し、慎重に制御する必要がある状態をカプセル化するために使用されます。
とても抽象的に聞こえますが、このように考えてもいいでしょう。
クラスでは、その中のコードがどのように動作するかを必ずしも知る必要はありません。 ただ、そのクラスが何を担っているのかが分かればいいのです。
クラスは、アプリケーションを well defined components の間の interactions という観点から推論することを可能にします。
これにより、アプリケーションがどのように動作するかを推論する際の認知的負担が軽減されます。 600行のコードが何を達成するのかを覚える代わりに、30個のコンポーネント**がどのように相互作用するかを考えることができるのです。
そして、その30個のコンポーネントは、おそらくアプリケーションの3層にまたがっていることを考えると、一度におよそ10個のコンポーネントについて推論する必要があるに過ぎないでしょう。
これなら、かなり管理しやすいと思います。
本来、先輩開発者たちがやっていることは、こうです。
彼らは、アプリケーションを 推論しやすい クラスに分解しているのです。
そして、それらを推論しやすい階層に整理しているのです。
これは、アプリケーションが大きくなるにつれて、全体として推論することが難しくなることを知っているからです。 レイヤーとクラスに分解することで、アプリケーション全体について推論する必要がなくなります。 アプリケーションの小さなサブセットについて推論する必要があるだけです。
なぜ、このようなDDDスタイル、たくさんのパターンが必要なのか、説明してください。
まず、注意点ですが、DDDで重要なのは「パターン」ではなく、「開発努力とビジネスの整合性」です。 グレッグ・ヤングは、青い本の各章は[間違った順序][1]になっていると発言しています。
これは、(a)ドメインの振る舞いを配管から区別する努力がなされているため、(b)ドメインモデルの概念が明示的に表現されていることを保証するために特別な努力がなされているためです。
率直に言って、もしドメインに2つの異なる概念があるならば、たとえメモリ上の表現が同じであっても、モデル上では区別されるべきなのです。
事実上、あなたは、ドメイン専門家が見てエラーを発見できるような、ビジネス言語でモデルを記述するドメイン固有の言語を構築しているのです。
さらに、懸念事項の分離や、ある能力の消費者を実装の詳細から隔離するという概念にも、もう少し注意が払われているようです。 D. L. Parnas]2を参照してください。 明確な境界があることで、ソリューション全体に影響を及ぼすことなく、実装を変更したり拡張したりすることができます。
ここでの動機は、ビジネスのコア・コンピテンシーの一部であるアプリケーション(つまり、競争上の優位性を引き出す場所)では、ドメインの動作をより優れたバリエーションに簡単かつ安価に置き換えることができるようにしたい、ということです。 事実上、プログラムには、急速に進化させたい部分(状態が時間とともにどのように進化するか)と、ゆっくりと変化させたい部分(状態がどのように保存されるか)があります。抽象化のレイヤーを増やすことで、不用意に一方を他方に結合してしまうことを避けることができます。
公平に見て、この中にはオブジェクト指向の脳障害もある。 Evansが最初に説明したパターンは、彼が15年以上前に参加したJavaプロジェクトに基づいている。そのスタイルでは、状態と振る舞いが緊密に結合しており、それはあなたが避けたいと思うような複雑さをもたらす。
どのような言語であっても、関数型スタイルでプログラミングをすることにはメリットがあります。便利なときはいつでもそうすべきだし、便利でないときはその判断についてよく考えるべき。 Carmack, 2012.