Skip to content

Instantly share code, notes, and snippets.

@sambatriste
Last active November 9, 2023 13:54
Show Gist options
  • Save sambatriste/5535b1f0f6a73e9b37239d86edf61c70 to your computer and use it in GitHub Desktop.
Save sambatriste/5535b1f0f6a73e9b37239d86edf61c70 to your computer and use it in GitHub Desktop.
「オブジェクト指向エクササイズ (ちょっとだけ)Groovy編」

オブジェクト指向エクササイズ

はじめに

オブジェクト指向エクササイズとは?

『ThoughtWorksアンソロジー』 ThoughtWorks Inc. (著) の第5章

https://images-na.ssl-images-amazon.com/images/I/51FOBZPjz-L.jpg

  • オブジェクト指向言語を使っていても「ドメインモデル貧血症」「トランザクション・スクリプト」に陥りがち
    • オブジェクトをリッチなものに育てるための方法
  • DDDに通じるものがある
  • シンプルでわかりやすい9つのルール

ルール

  1. 1つのメソッドにつきインデントは1段階までにすること
  2. else 句を使用しないこと
  3. すべてのプリミティブ型と文字列型をラップすること
  4. 1行につきドットは1つまでにすること
  5. 名前を省略しないこと
  6. すべてのエンティティを小さくすること
  7. 1つのクラスにつきインスタンス変数は2つまでにすること
  8. ファーストクラスコレクションを使用すること
  9. Getter, Setter, プロパティを使用しないこと

1. 1つのメソッドにつきインデントは1段階までにすること

(本からの引用)

Before

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();
    }
}

After

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");
    }
}

なぜ?

  • 複雑すぎるメソッドは凝集度が低い

どうする?

  • メソッドを分割する

2. else 句を使用しないこと

(本からの引用)

Before

public static void endMe() {
    if (status == DONE) {
        doSomething();
    } else {
        // その他のコード
    }
}

After

public static void endMe() {
    if (status == DONE) {
        doSomething();
        return;
    } 

    // その他のコード
    
}

Before

public static Node head() {
    if (isAdvancing()) {
        return first;
    } else {
        return last;
    }
}

After

public static Node head() {
    return isAdvancing() ? first : last;
}

なぜ?

  • 複雑すぎるメソッドは凝集度が低い
  • if-elseは、拡張されるにつれて肥大化する

どうする?

3. すべてのプリミティブ型と文字列型をラップすること

Before

class Member {
  // NG!!文字列型
  String name;
  // NG!!日付型
  Date birthday;
}

After

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とかも同様
  • プレゼンテーション層ではプリミティブ型、文字列を使って良い

4. 1行につきドットは1つまでにすること

Before

// NG!!1行にドットが2つある
member.getBirthday().isAdult();

After

member.isAdult();

class Member {
   boolean isAdult() {
     return birthday.isAdult();
   }
}

「同じでは?」と思うかもしれないが、 前者のようなコードがいろんなところで使われると非常に「うるさい」コードになる。

なぜ?

  • オブジェクトの内部構造が暴露されている。
  • 依存するオブジェクトの種類が増える。
□--->○
     /  \
    △ ◆
          \
          ☆

どうする?

  • 「Don’t talk to stranger」
    • 委譲メソッドを作る

オレオレルール

  • 自分の作ったクラス、特にmodelに適用する。
  • 一般的なAPI呼び出しは除外

  - Builderなどのメソッドチェーン

  • 「流れるようなインタフェース」

5. 名前を省略しないこと

Before

order.shipOrder();

After

order.ship();

Before

employee.getEmpName();

After

employee.getName();

なぜ?

  • 省略したくなる、短くしたくなるときはどういうときか?
    • 何度も同じ名前がでてくるとき
      • コードが重複している
      • 例:employee.getEmployeeName();
  • 名前重要

どうする?

  • 重複を取り除き端的な命名を
  • XXXInfoとかXXXManagerとか情報量少ないのでやめる

6. すべてのエンティティを小さくすること

  • 1ファイル50行まで
  • 1パッケージ10ファイルまで

なぜ?

  • 大きいクラス、パッケージは、複数の責務を持っている

どうする?

  • 1つの責務に分割するべし。
  • 他のルールを守ると自然と小さくなるはず

7. 1つのクラスにつきインスタンス変数は2つまでにすること

Before

class Name {
  // NG!!3つのインスタンス変数
  String first;
  String middle;
  String last;
}

After

class Name {
  Surname family;
  GivenNames given;
}

class Surname {
  String family;
}

class GivenNames {
  List<String> names;
}

なぜ?

たくさんの変数を持つクラスは凝集度が低い。

** どうする?

  • クラスを分割する。
  • ルール3や8を適用。

8. ファーストクラスコレクションを使用すること

Before

class Name {
  String first;
  // NG!!文字列型のコレクション
  List<String> givenNames;
}

After

class Name {
  Surname family;
  GivenNames given;
}

class GivenNames {
  List<String> names;
}

なぜ?

  • コレクション(List, Map等)は、いわば「プリミティブ型」のようなもの
  • 汎用的すぎて、オブジェクトが複数格納されていること以上の情報を読み手に与えない

どうする?

  • コレクションをフィールドに持つクラスを作る
    • 集計処理や抽出処理はここに記載する

9. Getter, Setter, プロパティを使用しないこと

Before

// 注文
class Order {
  // 単価
  int unitPrice;
  // 個数
  int quantity;
}

// NG!! 計算ロジックが、オブジェクトでなく手続きに配置されている
class OrderService {
  int calculateAmount(Order order) {
    int ammount = order.getUnitPrice() * order.getQuantity();
    return amount
  }
}

After

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パターンは煩雑になりがち

追加ルール10 可能な限りオブジェクトをイミュータブルにすること

なぜ?

状態を持つクラスは複雑になる

どうする?

ルール9を守るとわりと自然にイミュータブルになる。

  • コンストラクタで必要な情報を受け取る
  • 多すぎる場合はBuilderパターンを適用する
  • 更新したい、と思ったときは新しいインスタンスを生成する

オレオレルール

  • HogeHogeContextやBuilderみたいに情報を集めて回るのが目的なクラスは除外

コツのようなもの

いきなり全てのルールを完璧に守ろうとしない

  • コードが書けなくて手が止まってしまう
  • TODOコメントを書いて先に進む
  • 守るルールを減らす
    • 例:ルール9だけは守る
  • ルールを緩くする
    • 例:インスタンス変数は2つ -> 4つにする
  • とりあえず動くコードとテストを用意して、そこからスタート
    • Red->Green->Refactor

http://image.slidesharecdn.com/t8-140129100049-phpapp02/95/better-unit-tests-with-approvaltests-an-open-source-library-7-638.jpg?cb=1390995800

Webアプリ(DBを使う)は最初はやめとく

  • フレームワークやDBに引っ張られて、ルールを守りにくい。
    • Entityなど、JavaBeansであることを要求される
  • ツールやプラグイン等からやってみる

どんな場合でも有効、というわけではない

  • 簡単だとトランザクションスクリプトのほうがわかりやすかったり
    • これはDDDでも同じこと
  • コードの共同所有ができていないと難しい
    • モデルにロジックが集約すると、複数の会社に委託するとき
    • コンウェイの法則

参考

書籍

本は値段が高騰しているが、O’reilly Japanで電子書籍が入手可能 https://www.oreilly.co.jp/books/9784873113890/

草稿(英語)

http://www.xpteam.com/jeff/writings/objectcalisthenics.rtf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment