現場で役立つシステム設計の原則 - 読書感想文
はじめに
この記事は現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法を最後まで読んだのでその感想文を記したものです。なので本の要約を求めてた方、本の内容について深い考察を求めていた方、ごめんなさい。きっと要望を満たせていません。あくまでポエムとして見てください。
下記に注意事項を列挙しておきます。
- 読書感想文です。なので個人的な意見や感想が入ってます。
- 間違ったことを言ってる(書いてある)ことがあります。むしろ多くても不思議ではありません。
- 本の内容をしっかり知りたいなら本を買いましょう
この本を読もうと思ったきっかけ
感想を書いていく前にこの本を読もうと思った経緯を簡単に書いておきます。
元々はシステム開発を全くやったことのない部署にいたのですが会社都合で急遽システム開発チームに異動になりました。そこで「新規プロダクトをDDDでやるぞ」っと言われました。「何それ?」状態だったの帰宅してすぐネットで勉強するのに良い本がないか調べました。すると原典と言われるエリック・エヴァンスのドメイン駆動設計は難しくて挫折したというコメントが多くありました。なのでこれを読むことは避けました。
色々検索すると現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法が分かりやすくて良いぞ!というコメントやブログが山のように見つかったのでこれを購入して読むことにした、という流れです。
多分DDDを学びたい初心者あるあるの流れだと思います。
この本を読んだ感想
とりあえずこの本は2週読みました。1周目はあまり時間をかけずに一気に通読しました。2周目は章ごとにじっくりと読んで分かったことや疑問点を整理しながら次の章に行く、というスタイルを取りました。
2週読んで感じたことは主に以下の3つです。他にも色々言及することあるだろう!っと意見もあると思いますが、感想文があまりに長くなりそうなので今回はこの3つに絞ります。ごめんなさい。
「自己文書化」という言葉が腑に落ちた
自己文書化という馴染みの無い言葉(少なくとも私が)は第4章で出現して以下のような説明がされていました。
業務を分析しながら得た業務用語や言い回しは、そのままパッケージ名/クラス名/メソッド名/変数名になります。業務要件の説明としてもわかりやすく、プログラムの設計としても自然な名前を見つけることが、ドメインモデルの設計の目標です。このようにソースコードで業務の要求仕様を表現することをプログラムの自己文書化と呼びます。
この説明は私の中でDDDについて理解がグッと深まった気がしました。というのもこの本やネットで調べてもDDDの本質として「関心の分離」という言葉をよく目にしました。確かに業務フローや手順などと一緒にフレームワークやデータベースの実装が混ざっていると分かりにくいのは理解できますし、しっかりと分離するとテストもしやすいという説明も理解はできます。「読みやすかったりテストしやすいようにコードを書くのがDDDの本質なのだろうか?」っという疑問が自分の中でありました。そういう疑問を持った状態でこの説明を読んだ時、「DDDで実現したいことしたいことはコードをドキュメントとみなして自然言語のように読みやすいように表現することなのか?」という考えに至りました。もっと単純に言うと要求をそのままコードに落とし込みたいのかな?という印象です。
そして、この考えが仮に正しいなら他の章で説明していた内容と辻褄が合うような気もしてきました。その説明とは次のような内容です。いくつか抜粋します。
- コード内で空行やコメントを挿れて読みやすさを意識してたこと
- ドメインオブジェクトを業務用語と関連付けること
- 分かりやすい名前をつける(変数/関数/クラス/モジュールなど)
自然言語(英語)とコード
連続する文の間に空行を挿れると文章に余白が現れ、いくつかの文の塊が出現します。英語だとこの塊はパラグラフ(段落、節)と呼びます。パラグラフはこのサイトの解説では次のように説明しています。
英文のパラグラフは、単なる文の区切りではなく、内部に論理的構造を持った一つの文章のまとまりとなっている。(中略)一つのパラグラフの中では複数の主張をせず、一つの話題のみでまとめるのがベスト。
英文のパラグラフの例を示します。以下はWhat is Kubernetesの冒頭の文章です。視覚的にも文章が3つの塊に分かれて、それぞれがこのページはKubernetesの概略であること、Kubernetesとは何なのか?、Kubernetesの名前の由来について、説明していて意味あるパラグラフなのが分かります。
This page is an overview of Kubernetes.
Kubernetes is a portable, extensible, open-source platform for managing
containerized workloads and services, that facilitates both declarative
configuration and automation. It has a large, rapidly growing ecosystem.
Kubernetes services, support, and tools are widely available.
The name Kubernetes originates from Greek, meaning helmsman or pilot. K8s as an
abbreviation results from counting the eight letters between the "K" and the
"s". Google open-sourced the Kubernetes project in 2014. Kubernetes combines
over 15 years of Google's experience running production workloads at scale with
best-of-breed ideas and practices from the community.
一方で現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法の第1章ではコードの間に空行を挿れることを次のように説明しています。
空白行を使って「段落」に分け、段落ごとに「説明用の変数」を導入すると、一つひとつの段落の独立性が高くなります。この独立性の高くなった段落を「メソッド」として独立させると、さらにコードがわかりやすくなり、変更が楽で安全になります。
以下は実際に本に記載されていたサンプルコードです。
int price = quantity * unitPrice;
if(price < 3000)
price += 500; //送料
price = price * taxRate();
...
int basePrice = quantity * unitPrice;
int shippingCost = shippingCost(basePrice); //送料計算メソッド
int itemPrice = (basePrice + shippingCost) * taxRate();
英文もJavaのサンプルコードのどちらも共通していることは、文の塊は独立した1つの話題にのみ言及している点です。またprice
(価格)という変数を使い回すのではなく、おそらく業務上意味のある語彙である、basePrice
(基本価格)、shippingCost
(送料)、itemPrice
(商品価格)、taxRate
(税率)などを使って可読性を重視しています。
変数を使い回すことを辞めることで破壊的代入が無くなり副作用が起こりにくくなるというメリットもあります。これを英文として見た場合でも、元のコードは価格がどのように変わるか関心がいきますが、リファクタリング後のコードは読み飛ばしても最終的には商品価格を計算していることが一発でわかるというメリットがあります。
ドメインオブジェクト
上記のサンプルコードではでてきませんでしたが、他にも第1章から第3章で次のようなことが紹介されています。
- 値オブジェクト
- ファーストクラスコレクション
- 区分オブジェクト
いずれのクラスも業務で出てくる用語、関心事を表現するためのクラスとして説明されています。これらはドメインオブジェクトと呼ぶようです。また特定の関心事を小さなクラスとして実装することで、同じようなコードが複数箇所で書かれるのを防止すると同時に、仕様変更などの際に影響範囲などを限定・特定しやすくするという効果があるとも説明されています。個人的には他にも、用語に関する関心事がクラス内に隠蔽することによって、そのクラスを利用する側でその関心事を表現する必要がなく(というより表現しないほうがいい)、コードの可読性が抜群に上がる効果もあると思います。これはまさしくコードをドキュメントと見なしていて、それを重視しているのでは、と考えています。
そして、ちょうどこの感想文を書いている時、この本の著者が次のようなツイートをしていました。
なのでドメイン駆動設計の重要な要素の1つがコードをドキュメントとして扱う(みなす)ことなのだと思います。少なくとも著者はそういう視点を持っているな気がします。
ドメインモデルを育てる
コードをドキュメントと見なすこと、ドメインオブジェクトという小さなクラスを沢山つくること、これらが何の意味があるのか?という疑問が次に浮かんできました。もちろんコードが読みやすいので実装への理解がしやすくなるとか、クラスが小さいので使いまわしがしやすいとか、テストしやすいとか、様々なメリットはあると思います。ただこれらの理由も自分の中でなぜが納得感の低いものでした。
本を読み進めた結果、ドメインモデルを育てるため、だと思いました。「ドメインモデルを育てる」という表現は第5章で出てきて、節のタイトルにもなっています。平易で読みやすい本の中で何故かこの表現だけは抽象的ですが、適切な表現だと思いました。なぜならどう頑張っても完璧なドメインモデルを定義するのは難しく、何度も何度も作り直していくことで、ドメインモデルを良いものにしていく、というのを表現しているのだと思います。
なぜドメインモデルを正確に捉えて抽象化できないのか。この本ではインターネットの普及によって顧客の要望が多様化することしていることが原因と述べられていました。これは第9章に記載されています。
ソフトウェアの昔と今
たしかに思い返してみると、昔はソフトウェアといえば、◯太郎とか◯ーム◯ージビルダーなどのパッケージを家電量販店で購入するのが当たり前でした。いわゆる買い切りタイプのソフトでした。しかし現在では光ファイバーによる高速通信インターネット環境が普及したことにより、ソフトウェアのダウンロード購入が当たり前な時代です[1]。また常時接続が可能になったことでソフトウェアの修正パッチや追加機能などの提供をいつでも受けることが出来ます。
現代のソフトウェアは昔とは異なり売っておしまい、ではなく日々のアップデートが必要だと思っています。なぜならいつでもアップデートできる環境によって競合他社はユーザの要望を満たすように最新の機能をどんどん追加してくるからです。これに負けないようにするには、やはり自分達も同じようにユーザの要望を常日頃分析して、常にソフトウェアをアップデートし続ける必要があります。
この日々ソフトウェアをアップデートし続けることが、ソフトウェアを複雑にしていくのだと思います。著者も本の中(第9章)で次のように言及していました。
さらに次から次へと増改築を繰り返しているうちに、手が付けられないほど理解が難しく、おいそれとは変更ができない大きなコードのかたまりが生み出されます。
これは容易に想像できますし、実際に自分の担当しているプロダクトのコードはまさにこういう状態です。こうなるといずれソフトウェアのアップデートが困難もしくは不可能になります。またソフトウェアの不具合の特定や解決も難しくなっていくので途方にくれることになります。
このようにユーザの要望に答えるための機能やそれを実装するためのコードなどが複雑に絡まった状態にならないようにするために「関心の分離」が行われ、業務上の関心事をソフトウェアの都合から守ってあげるためにドメインオブジェクトが利用されるのだと思いました。またドメインオブジェクトを実装する上でオブジェクト指向プログラミングのカプセル化やポリモーフィズムと相性がよく、ドメイン駆動設計はオブジェクト指向とは何か理解している必要があるのだと思います。
ドメインモデルを育てるとは
オブジェクト指向で設計するによってソフトウェアが複雑になることを防ぐ(もしくは軽減する)ことができます。この何度も設計変更に耐えうる特性を変更容易性というようです。変更容易性を維持し続けるためにはリファクタリングが必要でしょうし、リファクタリング時に不具合を埋め込まないようにテストも重要になります。また変数名や関数名などに一貫性を持たせるためにはコーディング規約が必要を設けたり、コードレビューのルール整備も重要ですし、これらを継続してい活動するために継続的インテグレーション(CI)なども必要なると思います。要はとても手間も時間もかかる作業というわけです。
この手間と時間をかけながらドメインモデルを改善していく行為が、親が子供を立派な大人に成長できるように愛情を注いであげる行為と似ているので「育てる」という表現したのでは?と考えています[2]。私もこういう素敵な表現ができる人になりたい!
高凝集・疎結合が良い理由が分かった
ドメインモデルを育てる環境があればそれでいいのか?とも思いましたが、おそらく違うと思います。子育てだって衣食住と高校や大学にまで行かせてあげられる金銭的余裕があれば子供は立派な大人になるのか?と考えると「そんなことはないな」と思いました。何をもって「立派な大人」なのかは親によって考え方が違うと思いますし、「立派な大人指数」みたいな数値で可視化することもできません。いずれにしても育てるためには何かしらの指標が必要になると思います。
人間と違ってオブジェクト指向でソフトウェアの作る場合はある指標をもってドメインモデルを育てると良いと本には書かれていました。それは「高凝集・疎結合」です。本の中では次のように説明されています。
凝集とは「切っても切れない」関係です。オブジェクト指向のクラス設計は、この切っても切れない関係のデータとロジックを1つのクラスにまとめる「凝集」が基本です。凝集したクラスは意図が明確で使いやすくなります。同時に、そのクラス内部の変更が、ほかのクラスに影響しにくくなります。つまり疎結合になります。
以下のサンプルコードは本にこの説明が書かれていた箇所に記載されているものを少し改変したものです。
class Customer {
public constructor(
private firstName: string,
private lastName: string,
private age: number
) { }
public fullName(): string {
return this.firstName + " " + this.lastName;
}
public isAdult(): boolean {
return this.age >= 20;
}
}
この例だとクラスも小さく複雑な振る舞いが無いのでこれでも良さそうです。ただ凝集度という点で見るとこれは少し低いようです。なぜなら、例えば、fullName
メソッドはfirstName
とlastName
にのみ関連があってage
は別にあってもなくても関係がありません。つまり同じクラスにいるけど関係が無いこの状態を凝集度が低いと言うみたいです。
では凝集度を上げるにはどうするのかと言うと、本の中で幾度となく言及している「値オブジェクトやファーストクラスコレクションのようなドメインオブジェクトを使う」「小さなクラスを作る」「小さなクラスを部品として組み上げる」だと思います。以下は値オブジェクトを使ってリファクタリングしてみた例です。
class PersonName {
public constructor(
private firstName: string,
private lastName: string,
) { }
public fullName(): string {
return this.firstName + " " + this.lastName;
}
}
class Age {
public constructor(private age: number) { }
public isAdult(): boolean {
return this.age >= 20;
}
}
class Customer {
public constructor(
public personName: PersonName,
public age: Age
) { }
}
この例だと新たにPersonName
クラスとAge
クラスを追加しています。仮にCustomer
クラスでミドルネームを扱いたい、っとなった場合、前者ではコンストラクタにmiddleName
を与える必要があるのでCustomer
クラスを利用する全てに影響がでます。後者ではPersonNameクラスだけの変更で済み、Customer
クラスは何も修正がいらなくなります。このソースコードを修正した影響範囲が小さいほど疎結合と呼ぶのだと思います。
「高凝集・疎結合」の何が嬉しいのか?と考えると、修正の影響範囲を限定できるのでソースコードの修正が楽になる、つまり変更容易性を獲得できるからだと思います。この時に初めて「あぁ、なるほどー」となりました。これがドメインモデルを育てることに必要な要素で、それは変化の激しいユーザの要望に答えるためなのだな、っと全て繋がった気がしました。なので著者は本の中で一貫してオブジェクト指向の重要性を言い続けていたのだと思います[2:1]。
おわりに
今回は第6~8章あたりの「どうやって実装するのか」という点には触れてません。しかし書きたいことは大体書けたような気がします。もちろん、これらの章も非常に有用なトピックが多いのですが、この辺りは自分が実装する時に読み返したいなっと思います。特にイベントソーシングという技術は初見だったので非常に興味深く、今後活用される可能性もあるようなので注視したいと思います。
本を読んだ上でDDDで作る予定の業務のコードを読むと「この実装はこの子(ドメインモデル)のためになっているんだろうか?」のような親心のような気持ちになりました。またDDDに関しても「全くわからん」から「色々わからん」にグレードアップできたので得るものが多かったと思います。
個人的にはDDD関連をまだまだ学びたい点が多いので、今度は違う本に挑戦してみようかな?っと思ってます。例えば、ドメインモデルを見つけたり整理したりするのにどういう手法を使うか学ぶなら、おそらく、RDRAやUMLのようなことも学んだほうがいいんだろうなと思いました。他にもリファクタリングやテスト駆動開発、静的解析などのドメインモデルを育てる技術を学ぶ必要があると思います。もしくはDDDの原典といわれるエリック・エヴァンスのドメイン駆動設計を読むかもしれません。エヴァンスの経験や考察を学ぶことは、おそらく非常に有意義な気はします。ただし、単純に英語の技量を挙げないと内容を理解するのに手間取ります。やることが多い!
最後に「本を読み終わったので学んだことを整理するために感想文でも書くか」と思い書き始めましたが、半日ぐらいで終わると思ったら2週間ぐらいかかりました。ブログとか本とか書いてる人って本当にすげー!って思いました。
Discussion