現場で役立つシステム設計の原則
https://gyazo.com/87a39db73ea2861bc78855a6ee0bef51
読書開始日:2019/11/26
読書終了日:
1小さくまとめてわかりやすくする
設計とはソフトウェア全体をすっきりとした形に整える事です。
設計をする為に必要なもの綺麗なクラス図とかではなく、最終的にはソースコードになる。
どういう視点から整理して、どこに「何が書かれているのか?」があるかをわかりやすくする。それがソフトウェア設計です。
設計の善し悪いは、変更時にわかる
変更に大きなコストがかかるとき、それは設計が悪い事になっている。
うまく設計されたコードは変更が楽で安全である。
変更が大変なプログラムの特徴
メソッドが長い
特にIf-elseが入り混じったコードは理解に苦しむ
クラスが大きい
引数が大きい
引数が多い場合も関心事を詰め込んでいるため、変更するときにどの引数が関係しないかを見極める為に時間を要します。
変更をする度に変更が大変になる
ちょっとした修正が必要になり「変更」を加えた結果、時間とともに複雑化していきます。
このような「ちょっとした」コードの追加の繰り返しが、コードをより変更がしずらい構図になり、バランスが崩れる
プログラムの変更が楽になる
わかりやすい名前を使う
一文字や、略称系は使わない
正しい変数名を使うことは設計の第一歩である。
どのような業務のために何をしているプログラムなのかわかりやすくなります。
どこに何が書いてあるのぁを調べる事が、かんたんで確実になります。
長いメソッドは「段落」に分けて読みやすくする
切れ目がはっきりしないコードは読みづらい
空白行を入れて文章を書くように読みやすくする。
どの段落が何を担当しているのかがわかりやすくなるようにする
目的ごとに変数を用意する
1つの変数を使い回すのではなく、目的毎にローカル変数を使う
それぞれの計算のステップごとに専用のローカル変数を用意する。
この目的別に専用のローカル変数を用意し、コードの意図を変数名で説明する事を 説明用の変数の導入と呼ぶ。
リファクタリングの基本テクニック。
逆に1つの変数を使いまわして行う書き方を破壊的代入と呼びます。
この書き方は副作用が大きく、コードが安定しない。
メソッドとして独立させる
この方法はメソッドの抽出と呼ばれるリファクタリングの基本テクニックです メリッド
詳細をメソッドに移動するため、元のコードがシンプルになり読みやすくなる
メソッドの名前からコードの意図を理解しやすくなる。
メソッド内に変更の影響を閉じ込めやすくする
関連性が強いデータとロジックをメソッドに抽出すると、ロジックが再利用しやすい。
長いメソッドの複製は、プログラムの変更を面倒で危険な作業にします。
複製はコードの重複です。
メソッドの抽出はコードの複製を防ぎます。
メソッドを1回呼び出せば良いし、変更も少ない。
重複したコードをすべて変更するのはやっかいだし、危険です。
2019/11/30 2019/12/5
異なるクラスの重複したコードをなくす
手順1:
それぞれのクラスで該当するコード部分(段落)をメソッドに抽出する
手順2:
1つのクラスの参照関係がある場合: 参照する側でメソッドを呼び出しを行い、参照先のオブジェクトでメソッド呼び出しに書き換える
2つのクラスに参照関係がない場合:共通のメソッドの置き場所として、別のクラスを新たに作成し、元のクラスで抽出したメソッドを移動する
手順3
元の2つのクラスのメソッド呼び出しを、それぞれ新しいクラスの共通メソッドを利用するように書き換える
イメージがしやすいね
業務で使われる用語に合わせて、その用語の 関心事に対応するクラスをドメインオブジェクトと呼びます。
アプリケーションの対応領域 = ドメインに関心事を記述したオブジェクトという意味です domain:領地、領土、(知識・思想・活動などの)領域、分野、界、(土地の)完全所有権
application domainか
メソッドは短く、クラスは小さくが基本
名前は(略語ではなく)、普通の単語を使う
数行のコードを意味にある単位として「段落」に分ける
「目的別の変数」を使う(1つの変数を使いまわさない)
意味のあるコードのまとまり(段落)を「メソッド」として独立させる
変更に苦しむにはいつも長いメソッド、大きいクラスです。
2019/12/10
小さいクラスでわかりやすく安全に
プログラムの基本はデータを使った演算です。
基本データ
数値型
文字列型
日付型
基本データの落とし穴
intはマイナス21億からプラス21億の範囲
BigDeceimalは実質無限の数(小数点も21桁まで扱える)
値の範囲を制限してプログラムをわかりやすく安全にする
「値」を扱うための専用のクラスを用意する
業務で扱うように専用の型(クラスやインターフェース)を用意する
専用の型を用意する事で、ユーザからの不適切な値が混入するバグを防ぎます。
このような値の種類毎に専用に型を用意するとコードが安定し、コードの意図が明確になります。
値を扱うための専用クラスを値オブジェクト(Value Object)を呼びます。
値オブジェクトは業務で扱う情報の名前
業務上の判断や計算に使う用語
業務で扱うデータの種類ごとに値オブジェクトを上手く作って、「業務でやりたい事」と、「プログラムでやっている事」の間の対応を取りやすくします。
変数の上書きは危険である。
コードが複雑になり、思わぬ副作用を招く可能性がある
別の値が必要になったらその時点で新しいオブジェクトを生成する必要が出てくる。
1つのオブジェクトを使いまわしたら便利かもしれないが、オブジェクトの値が変わる事を前提にすると、そのオブジェクトがあの時点でどのような値を持っているのか、いつも心配することになります。
プログラムの変更の副作用を防止して、オブジェクトの用途を限定させます。
それがオブジェクト指向の良さを活かす設計です。
Value Objectを不変にさせる方法は
インスタンス変数はコンストラクタでオブジェクトの生成時に設定する
インスタンス変数を変更するsetterメソッドを作らない
別の値が必要であれば、別のインスタンス(オブジェクト)を作る
ちょい違うのかなぁ
これを完全コンストラクトとよぶ
もし、確実に確実に型を使って引数で渡ってくる値を判断したい場合は、タイプヒンティングを使う
2020/1/7
複雑さを閉じ込める
配列やコレクションはコードを複雑化する
for文などのループ処理内や
配列を扱うコレクション型を扱うロジックを専用クラスに閉じ込める
コレクション型を扱うクラスがclass List()とすると、そのクラスのインスタンス変数(ロジック)を扱う専用クラスに閉じ込める。
コレクション型のインスタンス変数を1つだけ持つ専用クラスです。
これをコレクションオブジェクト、またはファーストクラスコレクションと呼ぶ
コレクションオブジェクトは業務の関心事
2 場合分けのロジックを整理する
複雑なifやswitchは見通しが悪くなるので、コードの可読性が下がってしまう。
そこでロジックを整理させる方法
1.判定や処理の内容をそのままif()に書かず、メソッドで呼びだす
判定ロジックと分岐ロジックをメソッドに抽出するとそれぞれの判定が楽になります。
code:php
if(isStudent()){
return true;
}
function isMan() {
return this.age == '13'
}
2.elseを使わない。これを「ガード説」と言います
ガード説を使う事で、それぞれのif文が単体で動作する為、入れ替えても処理が動作します。
まぁでもこれだったらswitchでも良い気がするな
2020/1/23
久々の再開
複文は短文にわける
code:php
function free($age) {
if($age >= 18) return 'OK!';
if($age > 15) return 'So So';
if($age < 18) return 'NO!;';
}
$result = free(18);
echo $result;
複数の条件分岐(複文)は短文(1行判定)を使う事でシンプルな構造にする。
更にこの区分ごとのロジックをロジック毎に別クラス単位で行う
また同じ処理が出てくるのであれば、インターフェースを活用して、区分毎に同じクラスがあるかを確認する
インターフェースと区分毎の専用クラスを組み合わせて異なるクラスのオブジェクトを「同じ型」として扱うしくみを多態と呼ぶ
使う側のクラスが、実際にどのような区分があるのかを「知らない」事が重要です。クラスとクラス間が知っている事が増えれば密結合が高まる
結合が低いほど、そのクラスの独立性が下がり、あるクラスの変更が他のクラスに対する変更が大きくなります。
ここで紹介した区分で分けた場合で出来る「区分オブジェクト」は、値オブジェクトや、コレクションオブジェクトと同様に、業務の関心事やロジックに直接関連しています。
enumは多態をシンプルに記述する仕組みとなる
2020/1/24
3 業務ロジックをわかりやすく整理する
業務アプリケーションのコードの見通しが悪くなる原因
主に業務ロジックを整理する為にこの3つのインターフェースにわかれる
画面インターフェース
業務ロジック
データベース入出力
この3つの関心事を分離する為に3層アーキテクチャがある
プレゼンテーション層: 画面や外部接続インターフェース
アプリケーション層: 業務ロジック、業務ルール
データソース層: データベース入出力
このときアプリケーションの修正や拡張が必要になった場合に以下の状況になりがちです。
変更の対象箇所を特定するために、プログラムの広い範囲を調べる
1つの変更要求に対して、プログラムのあちこちの修正が必要
変更の副作用が起きていない事を確認する為の大量のテストが必要
これらのデータクラスと機能クラスを分ける手続き型の設計では以下の問題が出てきます。
同じ業務ロジックがあっちこっちに重複している
どに業務ロジックが書かれてあるか見通しが悪くなる
本来はデータクラスとロジックがそもそも別れてるのはおかしい。
クラスとは本ラシデータとロジックを1つのプログラミング単位にまとめる仕組みです。
ロジックを持たないデータクラスを使ってデータの受け渡しをすると、コードの重複が起きます。
データクラスを参照できる場所であれば、どのクラスにでも、ロジックを書けてしまう。
code:php
// このクラスは単純にデータを格納して呼び出しているだけで、業務ロジックはない
class Bad_DateWithoutLogic
{
private $value;
public function getValue()
{
return $this->value;
}
public function setValue($value)
{
$this->value = $value;
}
}
データクラスを使うと業務ロジックの見通しが悪くなる原因
原因
アプリケーション層の構造が画面の構造に引きずられる
アプリケーション層の構造がデータベースに都合に影響される
はい
画面毎にデータクラス(処理ファイル)を用意すると、1ページに対して1つのファイルが用意され、複数の機能クラスに同じ業務ロジックが重複されるようになります。なるな...
また機能クラスをCRUD単位で作成すると、Create、Read、Update ,Deleteを各ファイルで行うとなると、CRUDのロジックは一緒なはずなのに、別の機能クラスとして実装するパターンです。
共通機能ライブラリが失敗する理由
UtilクラスやCommonクラスでは、業務ロジックの共通をそれほど実現できない。
理由
汎用的な共通機能ライブラリの場合はビジネス要件によって、汎用的なクラスになりがちで、複数の引数を受け取ったりして、余計に複雑化します
色んな使い方に対応する為に汎用的にしても、結局は逆に使いづらいなります。
また用途ごとに細分化した共通関数の場合はメソッド数が増えるだけです。
疎結合ではなく、密結合になる
業務ロジックをわかりやすく整理する基本のアプローチ
データとロジックを一体にして業務ロジックを整理する
三層のそれぞれの関心事と業務ロジックの分離を徹底する
2020/2/4
データとロジックを一体にして業務ロジックを整理する
業務アプリケーションではデータクラスと機能クラスは「クラス」という仕組みを使っていますが、実際はデータ構造と処理手順という典型的な手続型の設計になっている。
本来のオブジェクト指向とは、データも機能も1つのクラスで管理すべきである
クラスのデータとそのデータを使う判断/加工/計算のロジックを一緒に書いておけば、コードの重複をなくせます。
呼び出すクラスでロジックを書かなくて良いからです。
その結果、プログラムの見通しがよくなり、修正の対象箇所が少なくなります。
クラス設計で必要な事は使う側のクラスのコードがシンプルになるように設計する事です。
このようなオブジェクト指向らしいクラスを設計するには次の点に気を配ります。
ロジックを整理する7つのルール
1.メソッドをロジックの置き場にする
2.業務ロジックをデータを持つクラスに移動する
3.使う側のクラスにロジックを書き始めたら設計を見直す
へぇ〜!
4.メソッドを短くして、ロジックの移動をやりやすくする
5.メソッドでは必ずインスタンス変数を使う
クラス内で完結させるのか
6.クラスが肥大化したら小さくわける
7. パッケージを使ってクラスを整理する
1 メソッドをロジックの置き場にする
コードを重複させない設計の基本はインスタンス変数を返すだけのgetterメソッドを書かない事です
code:php
class Person
{
private $firstName;
protected $lastName;
//これだけだとインスタンス変数を返すだけで何もしない
private function getFirstName() {
return $this->firstName;
}
private function getLastName() {
return$this->lastName;
}
}
上記のクラスはインスタンス変数を返すだけのロジックしかなく、何も役たってません。
メソッドにロジックをもたせましょう
code:php
class Person
{
private $firstName;
protected $lastName;
public function __construct($firstName,$lastName)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public function fullName() {
return "MyName is {$this->firstName}.{$this->lastName}";
}
2業務ロジックをデータを持つクラスに移動する
もし仮にデータを返すだけのgetterクラスを見つけたら、そのメソッドに何らかの判断/加工/計算をさせる事を考えます
そうする事で
データを持つ側のクラスにロジック(計算式)が増える
データをgetしていたクラスからロジック(計算式)が減る
使う側のクラスはデータをgetするのではなく、そのデータを使った計算結果を受け取るようになる
このようにして、データを持つクラスに業務ロジックを集める事がコードの重複や散在を防ぐ、オブジェクト指向の基本です。
続きはp80から
3使う側のクラスに業務ロジックを書き始めたら設計を見直す
データを持つクラスに、そのデータを使った判断/加工/計算のロジックを書くのがオブジェクト志向のクラス設計の基本となる。
初期の段階では、データだけを持ちデータを返すだけのクラスを作る事もあるし、データを持たないクラスに、データクラスからデータをgetして判断/加工/計算のロジックを書いてしまう事も初期の設計段階では問題ない。
問題なのは、とりあえず動くようになった後です。
つまり、データクラスとロジックを分けたクラスをそのままにして放置しておくのか・。
それとも、データクラスにロジックを移動して設計を改善するのかは、ソフトウェアの変更容易性を大きく左右させます。
仮に、もしデータクラスからgetして、そのデータを使って判断するロジックを書き始めたたら、何か変だと思うようにしましょう。
オブジェクト設計は最初から良い設計が生まれるわけではない。
コードを書いて、動かしてみながら、ロジックの置き場所や、クラス名/メソッド名の改善を続けて、よい設計を見つけていくのがオブジェクト指向の基本である。
4メソッドを短く書くとロジックの移動がやりやすくなる
長いメソッドを短いメソッドに分けると、本来ならばそのクラスにふさわしくないコードの塊を発見しやすくなります。
かつ、短くしたメソッドをデータクラスのロジックに寄せると、データとロジックのクラスが同じ場所にある方がプログラムがわかりやすくなります。
5メソッドは必ずインスタンス変数を使う
インスタンス変数を使わないメソッドは、そのクラスのメソッドとして不適切です。
6クラスが肥大化したら小さくわける
1つのクラスに沢山のインスタンス変数を扱う処理がでてきた場合は、ロジック(メソッド)に着目して、別クラスに切り出す。
メソッドが全てのインスタンス変数を使うようにする
このような関連性の高いデータとロジックだけを集めたクラス、凝集度が高いと言います。
凝集:切っても切れない関係
7パッケージを使ってクラスを整理する
パッケージスコープ = public宣言しないようにします。
パッケージに名前や、構造を改善していく事が、コードの見通しを良くし、どこに何が書いてあるかをわかりやすく保つ基本です。
業務ロジックを小さなオブジェクトに分ける
関連する業務データと業務ロジックを1つにまとめオブジェクトはドメインオブジェクトと呼びます。
ドメイン = 対象領域、問題領域という意味
ドメインオブジェクトは業務で扱うデータをインスタンス変数として持ち、その業務データを使った判断/加工/計算に業務ロジックを持つオブジェクトです。
業務で必要なロジックを小さく整理位してドメインオブジェクトを作成します。
更にそのドメインオブジェクト同士を結びつけて、大きな業務の関心事を表現するクラスを設計します
例)注文クラス
商品
数量
金額
納期
届け先
請求先
と、業務ロジックを分離する事ができる。
こうする事で、業務ロジックの全体を俯瞰して整理する事ができます。
しかし、こうなってくると、クラスの数が多くなってくる。
或いはファイルの数が
その場合は「パッケージ」を利用する。
さまざまなドメインオブジェクトを関心事の単位にグルーピングして、パッケージに分ける事で業務ロジックの全体を見通しよく整理できます。
ドメインオブジェクトの参照関係を整理する
業務アプリケーションの対象領域(ドメイン)をオブジェクトのモデルとして整理したものをドメインモデルと呼びます。
ドメインモデルは業務で扱うロジックとデータを集めて、整理したものです。
三層+ドメインモデルで関心事をわかりやすく分離する
https://gyazo.com/8480365a398b62fbf4a640f4813c176c
(より、全体を俯瞰した図になっている)
三層+ドメインモデルでは、業務ロジックを記述すうのはドメインモデルだけです。 更に拡張が必要な場合も、影響範囲の特定のドメインオブジェクトや、特定のパッケージに閉じ込めやすくしています。
データクラス方式では、業務ロジックはアプリケーション層の複数の機能クラスに重複します。また、アプリケーション層の構造が画面やデータベースに影響されやすく、その結果、業務ロジックがどこの書かれているかわかりません。
それに対して「ドメインモデル 方式」の3層構造では、全ての業務ロジックをドメインモデルに集めます。
その結果、記述がシンプルになり、役割が明確になります(?)
ここはもう少し具体例を見たい。
4ドメインモデルの考え方を理解する
業務が複座な場合、データクラスと機能クラスを分ける手続き型よりもオブジェクト指向で業務ロジックを整理するドメインモデルの方が良いと言われている
ドメインモデルで設計する狙い
業務的な判断/加工/計算のロジックを重複なく一元的に記述する
業務の関心事とコードを直接対応させ、どこに何が書かれてあるかわかりやすく整理する
業務のルールの変更や追加の時に、変更の影響の狭い範囲に閉じ込めを行う
ドメインモデルはプログラム言語で書かれた「業務の用語集」であり、「業務の説明書」です。
ただし、ただの単語の羅列ではなく、どのように相互に作用するかをパッケージ構成やクラスの参照関係で立体的に表現する手段です。
ドメインモデルの設計は難しいのか?
難しい要因の1つにオブジェクト指向プログラミングの経験が足りていない事がある。
データクラスとロジックを1つのクラスにまとめるオブジェクト指向を理解したとしても、どうしてもデータクラスと機能クラスを分ける手続き型に発送から抜け出す事ができない。
更に、要件定義や、分析のやり方がわからない場合です。 ドメインオブジェクトを設計する為にインプットとなる業務要件を、どうやって収集し整理するか。その分析からどのようにして、クラス設計をしていくのか、具体的にわからないのが難しいと感じてしまう要因です。
具体例でみていく
利用者の関心事とプログラミング単位を一致させる
ドメインモデルを作るにあたり、以下の2つの行為が必要です
人間のやりたい事を正しく理解する
要件の聞き取り
不明点を確かめる為の会話
図や表を使っての整理
図解した結果を記録する為の文章の作成
人間のやりたい事を動くソフトウェアとして実現する方法を考える
パッケージ構成と名前
クラス構成と名前
メンション構成と名前
名前付けが大事なんだなぁ~
分析クラスと設計クラスを一致させる
クラス図が有効になる
アプリケーションの図が、実際にクラス図でまとまっているか?
分析クラスは実装の事を考えなければ、自由度は高くなるが、モノごとを上手く説明できている分析クラスが、プログラミング単位としての設計クラスとして、適切とは限りません。
分析クラスでは業務の関心事を要点にしぼり説明する事が大事です。
その説明の中から業務ロジックをプログラムとして上手く記述できる設計クラスと見つける事がオブジェクト指向の分析設計になる 分析と設計を同じ人が担当する
2020/2/10
業務で使っている用語をクラス名にする
その用語がデータとロジックをひとかたまりとしたプログラミング単位として使えそうな事を検証します。
このようにして業務の関心事と、業務で使われている用語を理解しながら、プログラミングの構造を考えていくのがオブジェクト指向の分析設計のやり方です。
逆に業務で使われていない抽象的な概念の導入が分析や設計にブレークスルーをもたらす事もあります。
ごちゃごちゃしてわかりにくかったクラスのもつれた関係が、抽象的な概念を表すクラスの発見により、スッキリと整理できる事もあります。。
データモデルではなくオブジェクトモデル
手続き型では当たり前だったデータモデルの発送ではドメインモデルの設計はうまくいきません。
ドメインモデルは、対象業務(ドメイン)をオブジェクトの集合として表現する技法です。
アプリケーションの対象領域の関心事をデータとロジックが一体となったオブジェクとしての分析し、その分析結果をそのままクラス設計に反映させる手法がドメインモデルであり、ドメインオブジェクトです。
データクラスと機能クラスをわけるのではなく、データに対する判断/加工/計算の業務ロジックを一緒に考え、クラスとして整理していくオブジェクト指向で設計するのがドメインモデルです。
ドメインモデルとデータモデルは何が違うのか?
ドメインモデルは業務ロジックの整理の手法です。
業務データを判断/加工/計算する為の業務ロジックをデータをひとたまりにして「クラス」という単位で整理するのがオブジェクト指向の考え方です。
関心の中心は業務ロジックであり、データではありません。
一方でデータクラスの関心はデータが主役です。業務で発生する様々なデータを整理して、どうテーブルに記録するかを考えます。
(例)生年月日と年齢
1993/12/01 → 25
ドメインモデル:業務ロジックに関心がある
年齢が業務んも関心事である
年齢クラスを生成する
年齢クラスは内部的に生年月日インスタンス変数に持ち、そのインスタンス変数を使って年齢を計算するロジックがあります。
データモデル:記録しているデータに関心がある(DBのまま?)
年齢は記録すべきデータではありません。
結論計算の結果が欲しい。
テーブルには計算のもとになる生年月日だけを記録します。
手続き型の設計ではデータとロジックの整理を分けて考える為、年齢という関心事はデータモデルにはでてこない
なぜドメインモデルだと複座な業務ロジックを整理しやすいのか?
2020/3/5
p102
データモデルを中心にプログラムの設計を行うと、データクラスと機能クラスに分ける手続き型の設計になります
この設計だと、異なるクラスに同じロジックが重複しがちです。
つまり変更をする場合に大変な事になる。
一方で業務ロジックに変更に注目するオブジェクト指向のドメインモデルではこういう問題が起きにくくなります。
業務データとそのデータに関連する判断/加工/計算の業務ロジックを1つのクラスに集めて一元管理できるからです。
上記の例でいうと、年齢クラスは「年齢」という関心毎を整理します。
したがって、年齢を計算するロジックの置き場所は、年齢クラスだけです。
また大人料金か子供料金かを判定するロジックも年齢クラスに置ける
業務の関心ごとに大変するクラスを作成し、その関心事に関連するデータとロジックをそのクラスに集めて整理する事を繰り返します
オブジェクト指向で設計するドメインモデルは手続き型のプログラムにありがちな、業務ロジックを散財し重複する問題を解決する工夫です。
ドメインモデルをどうやって作っていくのか
部分を作りながら全体を組み立てていく
ドメインモデルとデータモデルでは、その分析と設計のやり方が異なります。
ドメインモデルはオブジェクト単位でプログラムのを整理する技法です。
データモデルはどのようなデータを扱う事が業務上必須かを整理することと、データを使った判断/加工/計算のロジックを記述する機能の設計を別々に進めます。
プログラムの規模が大きくなると、データモデルに対する変更は大変です。
オブジェクト指向のアプローチ
オブジェクト指向は部品に注目をしている。個々の部品を作り始めて、それを組み合わせながら段階的に作っていきます。
ボトムアップのアプローチになる
ある部分に注目して、その部分に必要なデータと、そのデータを使った判断/加工/計算を行うロジックに範囲を狭めます。
全体と部分を行ったり来たりしながら作る
ツールとしては
1.パッケージ図
2.業務図
がある。
p106まで
2020/3/23