🐤

ちょうぜつソフトウェア設計入門の要約 第一章

2023/02/28に公開

田中ひさてるさんの書籍を一通り読んだので、Kotlinという自分にとって身近な言語でアウトプットしてみた
何度も読み返したい書籍なので、復習しやすいようにまとめていく

第一章 クリーンアーキテクチャ

設計の役割と重要性、クリーンアーキテクチャとは何か?を解説した章

アーキテクチャはソフトウェアの開発速度に貢献する
(つまり、スクラッチで作って保守しないならそこまでこだわる必要もない)
アーキテクチャが整備されていれば、それを見ることで何をしたいのかがわかる
注:正しく動作することは保証しない。それを保証するのはテストの関心

クリーンアーキテクチャはあくまでも雛形。ただ0から自分で最適なアーキテクチャを考えることは困難なので、その雛形から始めるべき

汚い設計だと何が困るか?

  • コードの内容を把握するのにとても労力を要する
  • 改修時にあちこち直さないといけない
  • 逆に関係ないものがまとまっていて、改修が影響してしまう(壊れやすい)

余談
実際実務でMVCも守られていない大型システムの開発をしていましたが、その際も
この改修はどこに影響をもたらすのか? 対象はこれだけで本当に大丈夫なのか?
などなど常に不安を感じながら開発してました。

良い設計とは

凝縮度、依存関係、安定度に着目すると見えてくる

  1. 一つの関心が一つに閉じている(凝縮度)
  2. 利用する/されるの関係箇所を可能な限り減らす(依存関係)
  3. できるだけ変更頻度の高い事情に依存しない(安定度)←クリーンアーキテクチャは主にここに注目

1.凝縮度とは

関係の強いもののまとまり度合いのこと
関係ないものが含まれておらず、関係の強いものがまとまっていると高くなる
凝縮度が高い様子を、疎結合と呼ぶ(対義語は密結合)

2.依存関係とは

classなどでimportして他classのメソッドやプロパティを使用することがある
その時使う方は使われる方に依存しているという
これが依存関係

class Employee {
}

import Employee
class Shop {

}

のようなクラスがあったときに
ShopクラスはEmployeeクラスに依存している

EmployeeはShopクラスが消滅しても、不具合があったも成立する
でもShopクラスはEmployeeが無くなったらエラーで動かなくなる
たとえ、Shopが凝縮度の高い設計になっていたとしても、上記の依存関係の理由からEmployeeと結合度が高い状態になっており改修の際には芋づる式に直さないといけない

余談
今回は二つでしたが、これが何個何個も複雑に絡み合っていたら。。。
それは改修しにくいですよね

3.安定度とは

コードの変わりにくさのこと

前述のように依存関係が発生すると、変更の影響が波及する
ただ依存先がほぼ変わらないもの(安定度が高いもの)だったら、その心配もない

クリーンアーキテクチャのレイヤー

ではその良い設計というクリーンアーキテクチャはどのようなレイヤーで構成すると良いと言っているのか?
(注:下記のレイヤーがなければクリーンアーキテクチャではないというものではない。 あくまでもこう言ったレイヤーの分け方が良いのではないか?とクリーンアーキテクチャは言っている。具体的なレイヤーに関しては個別具体的に考える必要がある)

第一層 ドメインモデル

要求とは関係なく普遍的で、実態をそのまま映し取ったようなコードを集めたもの(=事実
これをドメインモデルと呼ぶ。
ドメインモデルは説明の通り安定度が高い
依存されることはあっても、依存することはない

ユーザーからの要求で変わる部分を排除していって、何を望まれても残るものだけを残す
そうすることによって出来上がってくる

第二層 ユースケース

ユーザが行いたい操作のロジックがまとまっているもののこと
ドメインモデルから排除された要求をここで実現する

書店であれば必ず仕入れと販売を行うので、そのロジックはドメインモデルにまとめる
販売した商品の情報を簿記の会計にどうまとめるかというのは、ユースケースで実現すべきこと

改修要望があった際に変更になることからもわかる通り、ドメインモデルよりは安定度が低い
(とはいえ、ドメインモデル以外への依存がないのでかなり安定度は高い)

第三層 インターフェースアダプタ

一層と二層だけではプログラムを呼び出せず実行できない状態
それらを外から呼び出し実行できるようにするのが、インターフェースアダプタ
この層の関心ごとはデータの入出力(=アプリケーションの枠組み)
Controller,Presenter,Main Routineなどが登場してくるのがこの層

  • Controller 上の層と下の層のデータの疎通を担当
  • Presenter データの受け渡しができるようにするための整形を担当
  • Main Routine 機能全体の中核となる処理を担当

エントリーポイントや、整形の役割を担うものが登場する層とも言える

第四層 インフラストラクチャ

具体的な技術に関する問題(=動作の実態)を担うのがこの層
DBMSや外部API、プロトコルなどのことを知って良いのはこの層まで

ここでの登場人物は

  • Repositry データのCRUDを行う
  • Gateway APIの呼び出しを行う
  • Mapper DBアクセスを行う
    などなど

外部に依存することからかなり不安定
(注:不安定とは言っても、依存先は大抵の場合)

たとえばHTTPSを使用していたがgRPCを使用することになった場合など、この層だけを改修すれば済む

このように層ごとに役割を分けることで、絡み合ったコードにならず改修しやすく内容を認識しやすくなる

クリーンアーキテクチャ実現の障壁

メリットはわかったとはいえ、処理順通りに実装して行っても実現することはできない。
たとえばユースケース層で今月の売り上げを簿記会計に従って集計しようとした際には、インターフェースアダプタの処理を呼び出してデータを取得しなければならない

// 単純に実装すると、UseCaseがControllerに依存してしまった。。。
import SalesController
class AccountingUseCase {
	fun aggregateSalesOnThisMonth() {
		val salesController = SalesController()
		val salessalesController.fetchSalesOnThisMonth()
		.
		.
		.
	}
}

そうするとユースケース層は、インターフェースアダプタ層に依存することになってしまう
この障壁の突破方法は次の記事で

Discussion