読んだ本
Clean Architecture 達人に学ぶソフトウェアの構造と設計
Robert C.Martin著
を読みました。
書評(読書感想文)です。
設計とアーキテクチャーの目的
著者はまず設計とアーキテクチャー(著者は設計=アーキテクチャーだと言っている)の目的から説き起こします。曰く
`「ソフトウェアアーキテクチャーの目的は求められるシステムを構築・保守するために必要な人材を最小限に抑えることだ」
一瞬「んっ?」となって、しばらく後にそうかも知れないな、と思いました。これは趣味の話ではなく、ビジネスとしてソフトウェアを開発する場合の話をしているんですね。仕事なのに、結構趣味的に作っている人、保守なんて知らない〜、と思っているエンジニアにはピンと来ない話かもしれませんが、ビジネス視点から見ると確かに正しいですね。
ちなみに、自分はずっと楽しみながらソフトウェアを作って成長してきた(つもり)のエンジニアなので決してビジネスに捉われないで楽しむエンジニアが悪いとは思いません。(ただ部下に持つと苦労するなあ・・・)
別の言い方をすると、
「開発と保守で生産性が悪いソフトウェアは設計が悪い」
ということでしごくごもっともです。
なにか修正するたびに莫大な人月が必要なアレです。しかも月日が経つにつれてどんどん悪化していく。
では、どうすれば真っ当な設計になるのでしょうか?
設計ってどうすれば?
第2部までは、歴史と振り返りって感じですのでとりあえず飛ばして、第3部の
SRP:単一責任の原則
OCP:オープン・クローズドの原則
LSP:リスコフの置換原則
ISP:インターフェイス分離の原則
DIP:依存関係逆転の原則
このあたりですか?
これらは頭文字をとってSOLID原則と言われています。
これらはシステムの**「基本設計」というよりは「詳細設計」で有効な考え方です。著者も「コードレベルよりも上に適用するものであり」と言っており、さらにこの先にはコンポーネント設計が待っています。
コンポーネント設計まで行けば「基本設計」**の範疇に入るのでは無いでしょうか。
しかし、まずは「SRP:単一責任の原則」
SRP:単一責任の原則
本書ではなんだかやたらとわかりにくい説明ですが、自分の理解ではこうなります。
責務はMECEであるべきである
MECEって何?という方は、
「MECE(ミーシー)」とは何か、5つのフレームワークの基本を学ぶ
あたりをご覧ください。簡単に言うと「もれなく、ダブリなく」という意味の「Mutually Exclusive and Collectively Exhaustive」の頭文字を取ったのがMECEです。もともとマッキンゼーの社内用語で、最近ではロジカルシンキングに関する話題で良く目にすると思います。
MECEなら何でも良いというわけではないと思いますが、「単一責任の原則」を説明するにはとても良い概念ではないでしょうか。
この考え方は、すべての事象の分析に有効であって、詳細設計レベルではなく基本設計、要件的などのレベルでも適用されて価値あるものだと考えます。
OCP:オープン・クローズドの原則
カプセル化と依存の方向を考慮することで、「既存の成果物を変更せずに拡張できるようにすべき」ということを言っています。
既存の成果物とは何かが問題なのですが、基本的には
ビジネスロジックなどの中核部分
であったり
階層構造において低レイヤーに位置し上層から呼び出される側
(一般的にという事です)
ということでしょう。
これら既存の成果物と呼ばれるクラス・モジュール・コンポーネントなどは、当然ながら「単一責任の原則」によって責務が正しく与えられている必要があります。
責務が正しく分割されていて、外部から呼び出されるだけ
のクラス・モジュール・コンポーネントなどは、当然外部の変更に対して隔離されているのでオープン・クローズドの原則に従っていると言えます。
LSP:リスコフの置換原則
Swiftで言えばProtocol、JavaではInterface、C++では抽象クラスの実装オブジェクトは、どのオブジェクトでもこれらのインターフェイス定義の通りの振る舞いをするべき、という原則と理解です。
インターフェイス定義が正しく明快であれば、その通りに実装するだけなので注意する必要はありますが、さほど重要とは思えません。
ISP:インターフェイス分離の原則
「他に不必要に依存するな」「依存させるな」というのがこの原則だと思います。
責務の分割がMECEであって、「オープン・クローズドの原則」をつかって依存関係を最小限にとどめ、適切なインターフェスを設計すれば、自ずから依存は減少するでしょう。
あえて言えば、下記「依存関係逆転の原則」で出てくるようなインターフェイスへの依存において、あるクラスが複数のインターフェイスを定義し、不必要な機能の実装を相手のクラスに強要しない、といった考え方は重要であるかもしれません。
DIP:依存関係逆転の原則
ほぼ読書感想文じゃなくなってきましたが、ここでも自分の意見を書いていきたいと思います。
まず依存性とは結合の方向のことです。「オープン・クローズドの原則」でも出てきましたが、通常のソフトウェアの構造化で、Aが上位層(以下A層)、Bが下位層(以下B層)に位置するとすると、AがBに依存したとしてもBはそもそも変更が少ないためにあまり問題は起きないだろう、と考えます。
しかしながら上位層のAこそが不変のモジュールであってBに依存したくない。しかしBの機能を呼び出さざるを得ない場合はどうすれば良いでしょうか。
その答えが**「依存関係の逆転」**です。
具体的にはインターフェイス(SwiftのProtocol、JavaのInterface)に依存し、実装(オブジェクトインスタンス)に依存しなければいいですよね?というのが「依存関係逆転の原則」です。
上図のような状態では、AもBもインターフェイスに依存しています。Aはインターフェイスの定義の通りBの機能を呼び出すだけでBが何者かは問題にしていません。Bはインターフェイスの定義に背かないように(「リスコフの置換原則」)実装することでAに依存することはありません。このA層のインターフェイスの依存することで逆転という表現を使っているのだと思います。
しかし少々疑問に感じるのは、逆転しているかどうかはインターフェイスの持ち主に寄るのではないか、ということです。
つまりそのインターフェイスの定義の持ち主はAなのか、Bなのか。
上図のような構成を考えると、特に逆転とは言えません。B層はある狙いを持ってインターフェイスを定義してBのインスタンスを隠蔽していると思われます。
このように、インターフェイスに依存すること自体がある設計パターン上意味を持つのであって、逆転云々は気にしなくても良いな、というのが正直なところです。
まとめ
Clean Architecture 達人に学ぶソフトウェアの構造と設計の内容というよりも、ほぼ自分の考えを書いただけになりましたが、一度まとめておきます。
クラス・モジュール・コンポーネントの詳細設計において重要なのは、
責務はMECEになるように考えること
依存の方向を考えること
不必要に依存するな、依存させるな、ということ
必要であればインターフェイスに依存することを考えること
だと思います。
ほぼ分割と依存について述べられています。
最後に下記に目次をあげておきます。
第4部第5部についてはいつか書けるでしょうか・・・
目次
第1部 イントロダクション
設計とアーキテクチャー
2つの価値のお話
第2部 構成要素から始めよ:プログラミングパラダイム
パラダイムの概要
構造化プログラミング
オブジェクト指向プログラミング
関数型プログラミング
第3部 設計の原則
SRP:単一責任の原則
OCP:オープン・クローズドの原則
LSP:リスコフの置換原則
ISP:インターフェイス分離の原則
DIP:依存関係逆転の原則
第4部 コンポーネントの原則
コンポーネント
コンポーネントの凝縮性
コンポーネントの結合
第5部 アーキテクチャ
アーキテクチャーとは?
独立性
バウンダリー:境界線を引く
境界の解剖学
方針とレベル
ビジネスルール
叫ぶアーキテクチャー
クリーンアーキテクチャー
プレゼンターとHumble Object
部分的な境界
レイヤーと境界
メインコンポーネント
サービス:あらゆる存在
テスト境界
クリーン組み込みアーキテクチャー
第6部 詳細
省略
第7部 付録
省略