『ThoughtWorksアンソロジー』 ThoughtWorks Inc. (著) の第5章
- オブジェクト指向言語を使っていても「ドメインモデル貧血症」「トランザクション・スクリプト」に陥りがち
- オブジェクトをリッチなものに育てるための方法
- DDDに通じるものがある
- シンプルでわかりやすい9つのルール
- 1つのメソッドにつきインデントは1段階までにすること
- else 句を使用しないこと
- すべてのプリミティブ型と文字列型をラップすること
- 1行につきドットは1つまでにすること
- 名前を省略しないこと
- すべてのエンティティを小さくすること
- 1つのクラスにつきインスタンス変数は2つまでにすること
- ファーストクラスコレクションを使用すること
- Getter, Setter, プロパティを使用しないこと
(本からの引用)
class Board {
String board() {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < 10; i++) {
// NG!! インデントが2段階
for (int j = 0; i < 10; j++) {
buf.append(data[i][j]);
}
buf.append("\n");
}
return buf.toString();
}
}
class Board {
String board() {
StringBuffer buf = new StringBuffer();
collectRows(buf);
return buf.toString();
}
void collectRows(StringBuffer buf) {
for (int i = 0; i < 10; i++) {
collectRow(buf)
}
}
// 1行あたりの処理が抽出された
void collectRow(StringBuffer buf, int row) {
for (int i = 0; i < 10; i++) {
buf.append(data[row][i]);
}
buf.append("\n");
}
}
- 複雑すぎるメソッドは凝集度が低い
- メソッドを分割する
(本からの引用)
public static void endMe() {
if (status == DONE) {
doSomething();
} else {
// その他のコード
}
}
public static void endMe() {
if (status == DONE) {
doSomething();
return;
}
// その他のコード
}
public static Node head() {
if (isAdvancing()) {
return first;
} else {
return last;
}
}
public static Node head() {
return isAdvancing() ? first : last;
}
- 複雑すぎるメソッドは凝集度が低い
- if-elseは、拡張されるにつれて肥大化する
- ガード節
- Strategyパターン
- NullObjectパターン
- エスイーが要件定義でやるべきたったひとつのこと(実践編)
class Member {
// NG!!文字列型
String name;
// NG!!日付型
Date birthday;
}
class Member {
Name name;
Birthday address;
}
class Birthday {
// 誕生日の日付(未来日は不可)
Date birthday;
// 現時点の年齢
int calcCurrentAge() {}
// 干支
Eto getEto() {}
// 成人かどうか
boolean isAdult() {}
}
- プリミティブ型、文字列は「意味」や「制約」を持たない。
- 得点なのか身長なのか金額なのか
- 名前なのか住所なのかポエムなのか
- 適切な振る舞いを持てない
- タイプセーフでない
int height = man.getHeight();
// NG!! 体重を渡すべきところに身長を渡している
boolean fat = isFat(height);
- 格納する内容を表すクラスを抽出する
- 日付も同じ扱いとする
- 誕生日なのか支払期日なのか
- BigDecimalとかも同様
- プレゼンテーション層ではプリミティブ型、文字列を使って良い
// NG!!1行にドットが2つある
member.getBirthday().isAdult();
member.isAdult();
class Member {
boolean isAdult() {
return birthday.isAdult();
}
}
「同じでは?」と思うかもしれないが、 前者のようなコードがいろんなところで使われると非常に「うるさい」コードになる。
- オブジェクトの内部構造が暴露されている。
- 依存するオブジェクトの種類が増える。
□--->○ / \ △ ◆ \ ☆
- 「Don’t talk to stranger」
- 委譲メソッドを作る
- 自分の作ったクラス、特にmodelに適用する。
- 一般的なAPI呼び出しは除外
- Builderなどのメソッドチェーン
- 「流れるようなインタフェース」
order.shipOrder();
order.ship();
employee.getEmpName();
employee.getName();
- 省略したくなる、短くしたくなるときはどういうときか?
- 何度も同じ名前がでてくるとき
- コードが重複している
- 例:employee.getEmployeeName();
- 何度も同じ名前がでてくるとき
- 名前重要
- 重複を取り除き端的な命名を
- XXXInfoとかXXXManagerとか情報量少ないのでやめる
- 1ファイル50行まで
- 1パッケージ10ファイルまで
- 大きいクラス、パッケージは、複数の責務を持っている
- 1つの責務に分割するべし。
- 他のルールを守ると自然と小さくなるはず
class Name {
// NG!!3つのインスタンス変数
String first;
String middle;
String last;
}
class Name {
Surname family;
GivenNames given;
}
class Surname {
String family;
}
class GivenNames {
List<String> names;
}
たくさんの変数を持つクラスは凝集度が低い。
** どうする?
- クラスを分割する。
- ルール3や8を適用。
class Name {
String first;
// NG!!文字列型のコレクション
List<String> givenNames;
}
class Name {
Surname family;
GivenNames given;
}
class GivenNames {
List<String> names;
}
- コレクション(List, Map等)は、いわば「プリミティブ型」のようなもの
- 汎用的すぎて、オブジェクトが複数格納されていること以上の情報を読み手に与えない
- コレクションをフィールドに持つクラスを作る
- 集計処理や抽出処理はここに記載する
// 注文
class Order {
// 単価
int unitPrice;
// 個数
int quantity;
}
// NG!! 計算ロジックが、オブジェクトでなく手続きに配置されている
class OrderService {
int calculateAmount(Order order) {
int ammount = order.getUnitPrice() * order.getQuantity();
return amount
}
}
class Order {
int unitPrice;
int quantity;
int calculateAmount() {
return unitPrice * quantity;
}
}
- データ格納オブジェクト(なんとかBean)と手続きオブジェクト(なんとかService)とに分割される
- カプセル化が破られる
- データと振る舞いが一体になっているというのが元々のオブジェクトの考え方
- JavaBeansは構造体とほとんど変わらない
- ドメインモデル貧血症に直結する
- 『リファクタリング』本の「属性・操作の横恋慕」に該当
- 「Tell, Don’t ask.」求めるな、命じよ
- 情報をgetして何かする、のではなく、してほしいことを依頼する
- getterを呼びたくなった時は「それをgetしたあとで何をするのか」を考え、その処理を取得元に持っていく。
- プレゼンテーション層(JSPやテンプレートエンジン)で、表示用にgetterを呼び出すのはOK
- モデルに表示ロジックを入れたくない
- Visitorパターンは煩雑になりがち
状態を持つクラスは複雑になる
ルール9を守るとわりと自然にイミュータブルになる。
- コンストラクタで必要な情報を受け取る
- 多すぎる場合はBuilderパターンを適用する
- 更新したい、と思ったときは新しいインスタンスを生成する
- HogeHogeContextやBuilderみたいに情報を集めて回るのが目的なクラスは除外
- コードが書けなくて手が止まってしまう
- TODOコメントを書いて先に進む
- 守るルールを減らす
- 例:ルール9だけは守る
- ルールを緩くする
- 例:インスタンス変数は2つ -> 4つにする
- とりあえず動くコードとテストを用意して、そこからスタート
- Red->Green->Refactor
- フレームワークやDBに引っ張られて、ルールを守りにくい。
- Entityなど、JavaBeansであることを要求される
- ツールやプラグイン等からやってみる
- 簡単だとトランザクションスクリプトのほうがわかりやすかったり
- これはDDDでも同じこと
- コードの共同所有ができていないと難しい
- モデルにロジックが集約すると、複数の会社に委託するとき
- コンウェイの法則
本は値段が高騰しているが、O’reilly Japanで電子書籍が入手可能 https://www.oreilly.co.jp/books/9784873113890/