[現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法](https://gihyo.jp/book/2017/978-4-7741-9087-7) のメモ 本のサンプルソースはJavaで書かれているが、勉強も兼ねてGo言語にアレンジしている。 # Chapter1 小さくまとめてわかりやすくする ## なぜソフトウェアの変更は大変なのか ### ソフトウェアの変更に立ち向かう ソフトウェアに修正や拡張はつきもの。 変更はやっかいな作業。 なぜなら慎重に変更しても思わぬ副作用がでることがある。 それは設計が悪い。 設計とはソースコードを整理整頓してどこにあるかわかりやすくすること。 修正に3日かかるのか、半日にで終わるのか。その違いを生むのが設計。 ### 変更が大変なプログラムの特徴 メソッドが長い。 クラスが大きい。 引数が多い。 ### 変更するたびに変更が大変になる。 初めは変更が容易なプログラムだったはず。 ちょっとしたちょっとしたコードの追加の繰り返しで、構造が入り組んでくる。 変更をするたびに変更が大変になっていくという負のスパイラルから抜け出すためにはどうすればいいか? ## プログラムの変更が楽になる書き方 ### わかりやすい名前を使う |1文字の変数名 |省略した変数名 |意味のある単語を使った変数名 | |--|--|--| | int a| int qty | int quantity <br/> ※これがベスト| > [name=Yusaku Matsuki] golangは短いものが好まれる。 > - [CodeReviewComments · golang/go Wiki](https://github.com/golang/go/wiki/CodeReviewComments#variable-names) ### 長いメソッドは「段落」に分けて読みやすくする ``` # bad price := quantity * unitPrice if price < 3000 { price += 500 // 送料 } price = int(float64(price) * taxRate()) ``` ``` # good price := quantity * unitPrice if price < 3000 { price += 500 // 送料 } price = int(float64(price) * taxRate()) ``` ### 目的毎に変数を用意する ``` # bad price := quantity * unitPrice if price < 3000 { price += 500 // 送料 } price = int(float64(price) * taxRate()) ``` ``` # bad basePrice := quantity * unitPrice shippingCost := 0 // 送料の初期値 if basePrice < 3000 { shippingCost += 500 // 送料 } itemPrice := int(float64(basePrice + shippingCost) * taxRate()) ``` ### メソッドとして独立させる ``` # before basePrice := quantity * unitPrice shippingCost := 0 // 送料の初期値 if basePrice < 3000 { shippingCost += 500 // 送料 } itemPrice := int(float64(basePrice + shippingCost) * taxRate()) ``` ``` # after basePrice := quantity * unitPrice shippingCost := shippingCost(basePrice) // 送料の初期値 itemPrice := int(float64(basePrice + shippingCost) * taxRate()) ... func shippingCost(basePrice int) int { if basePrice < 3000 { return 500 // 送料 } return 0 } ``` メインの処理がスッキリする。 送料計算のルールを変更するとき修正箇所をメソッド内部に限定できる。 ***メソッドの抽出***と呼ぶ。 ロジックを再利用しやすくなる。 ### 異なるクラスの重複したコードをなくす ``` package shippingcost // 送料クラス // packageとstructでclassのようなものを定義 const ( minimumForFree = 3000 cost = 500 ) type ShippingCost struct { basePrice int } func NewShippingCost(basePrice int) ShippingCost { return ShippingCost{ basePrice: basePrice, } } func (shippingCost ShippingCost) amount() int { if shippingCost.basePrice < minimumForFree { return cost } return 0 } ``` ``` // 送料クラスを利用するコード func shippingCost(basePrice int) int { shippingCost := NewShippingCost(basePrice) return shippingCost.amount() } ``` 用途を限定した小さなクラスがロジックの再利用を促しコードの重複を防ぐ。 ### 狭い関心事に特化したクラスにする TBD ### メソッドは短く、クラスは小さく TBD ## 小さなクラスでわかりやすく安全に ### データとロジック TBD ### 基本データ型の落とし穴 > [name=Yusaku Matsuki] > プリミティブオブセッションの話に近いと思う > - [Refactoring to an Adaptive Model - Remove primitive obsession ](https://www.martinfowler.com/articles/refactoring-adaptive-model.html#RemovePrimitiveObsession) > > 関連してCode smellというものもある。 > - [コードの臭い - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E8%87%AD%E3%81%84) ### 値の範囲を制限してプログラムをわかりやすく安全にする ### 「値を」扱うための専用クラスを作る 値を扱うための専用クラスを作るやり方を***値オブジェクト(Value Object)***と呼ぶ。 > [name=Yusaku Matsuki] > Clean Architectureでのドメイン層。 ### 値オブジェクトは「不変」にする 値ごとに別のオブジェクトを用意することで、ひとつひとつのオブジェクトの用途が限定される。 オブジェクトの値が変わることを前提にすると、「そのオブジェクトが」「ある時点でどのような値を持っているのか」いつも心配することになる。 Value Objectは扱えるデータの種類や範囲を限定した独自のデータ型。 そのValue Objectを不変にすることで、オブジェクトは1つの値を持った、用途を限定したオブジェクトになる。 Value Objectの作り方。 - インスタンス変数はコンストラクタでオブジェクトの生成時に設定する - setterを作らない - 別の値が必要な場合、別インスタンスを作る ### 「型」を使ってコードをわかりやすく安全にする TBD ## 複雑さを閉じ込める ### 配列やコレクションはコードを複雑にする 配列やコレクション型を操作するロジックを、専用の小さなクラスにまとめることで、変更を容易にする。 ### コレクション型を扱うコードの整理 コレクション型はデフォルトでいくつかメソッドが用意されている。 しかし、業務ロジックを表現するには物足りず、for分やif文を用いて複雑な処理が必要になる。 ループ構造が入れ子になる、if文の場合分けが増えるなど、コードを理解することが難しくなる。 また、上記のようなプログラムがあちこちに散らばると状況はさらに悪化する。 どうすればよいか? ### コレクション型を扱うロジックを専用クラスに閉じ込める Value Objectと同じ用にデータと関連するロジックを1つにまとめる。 コレクション型のデータとロジックを特別扱いにして、コレクションを一つだけ持つ専用のクラスを作る。 ***コレクションオブジェクト***または***ファーストクラスコレクション***と呼ぶ。 > [name=Yusaku Matsuki] > ドメインサービスに近いもの? > - [実践DDD本 第7章「ドメインサービス」~複数の物を扱うビジネスルール~ (1/4):CodeZine(コードジン)](https://codezine.jp/article/detail/10318) > - [DDD+CQRSでのコレクション操作考察 - Qiita](https://qiita.com/kwhrkzk/items/0fb3bc4f54a6d27e9bcc) ### コレクションオブジェクトを安定させる Value Objectと同じ用にできるだけ「不変」を保つ。 コレクションの参照をそのまま渡すメソッドを用意しては行けない。 なぜなら要素の追加や削除がクラスの外部からできてしまうため。 コレクションの操作を安定させる方法は3つ。 - コレクション操作のロジックをコレクションオブジェクトに移動する - コレクション操作の結果も同じ型のコレクションオブジェクトとして返す - コレクションを「不変」にして外部に渡す 捜査対象のデータを持つクラスにロジックを集めることがオブジェクト指向設計の基本。 データを持つクラスにロジックを集めることで使う側のクラス、使われる側のクラスのどちらのコードもシンプルに保てる。 addやremoveのようにコレクションオブジェクトの要素を変更する操作は、別のコレクションオブジェクトを作成し操作結果を返すやり方がある。 こうすることで個々のコレクションオブジェクトは内部のコレクションの状態が変化しない不変スタイルのオブジェクトになる。 結果として、コレクション操作を行っても副作用が起きにくくなる。 TBD ### コレクションオブジェクトは業務の関心事 TBD ## 第一章のまとめ TBD ###### tags: `ISBN-978-4-7741-9087-7`