「現場で役立つシステム設計の原則」まとめ ~ 2章 ~

システム設計の原則知ってますか?

この記事は「現場で役立つシステム設計の原則」を読んで勉強になったことや仕事で役に立ちそうなことをまとめた記事です。 あくまでまとめで、説明できなさそうなところは省いているのできちんと理解したい人は本を買って読んでください!

1章のまとめはこちら「現場で役立つシステム設計の原則」まとめ ~ 1章 ~

場合分けのロジックを整理する

区分や種別がコードを複雑にする

顧客区分・料金種別・商品分類のように区分や分類を書き分ける基本手段がif文やswitch文だが、プログラムにいろいろな箇所に同じようなif/switch文が重複すると、区分追加や区分の変更の際にプログラムの変更がやりにくくなる。

判断や処理のロジックをメソッドに独立させる

1章のオブジェクト指向の考え方に基づき以下のようにコードを整理する。

  • コードの塊は、メソッドとして抽出して独立させる
  • 関連するデータとロジックは、1つのクラスにまとめる
// if文の中にロジックがあるのでBad
if(customerType.equals("child")) {
    fee = baseFee * 0.5;
}

// メソッドに抽出されているのでGood
if (isChild()) {
    fee = childFee();
}

// メソッド定義は省略

else句をなくすと条件分岐が単純になる

else句はプログラムを複雑にするので、else句をできるだけ書かないようにするとプログラムが単純になる。

ローカル変数に結果を持たせreturnするのではなく、条件が一致した時にreturnするようにすればコードがシンプルになる。これを早期リターンという。

またelse句を使わずに早期リターンをする書き方をガード節と呼ぶ。

// Bad
Yen fee() {
    Yen result;
    if(isChild()) {
        result = childFee();
    } else if(isSenior()){
        result = seniorFee();
    } else {
        result = adultFee();
    }
    return result;
}

// 早期リターンを使うとこうなる
Yen fee() {
    if(isChild()) {
        return childFee();
    } else if (isSenior()) {
        return seniorFee();
    } else {
        return adultFee();
    }
}

// 早期リターンを使うことでelse句をなくすことができる(ガード節)
Yen fee() {
    if(isChild()) {
        return childFee();
    }
    if(isSenior) {
        return seniorFee();
    }
    return adultFee();
}

区分ごとのロジックを別クラスに分ける

区分に関するロジックを区分ごとのクラスに分けて記述することで、区分ごとにロジックが整理されるので、どのにロジックがあるかが明確になる。

// 大人料金クラス
class AdultFee {
    Yen fee() {
        return new Yen(100);
    }

    String label() {
        return "大人";
    }
}
// 子供料金クラス
class childFee() {
    Yen fee() {
        return new Yen(50);
    }

    String label() {
        return "子供";
    }
}
// シニア料金も同様にクラスに分ける

インターフェースを使って区分ごとのクラスを同じ「型」として扱う

区分ごとにクラスにわけるとロジックはわかりやすくなるが、呼び出し側でどのクラスを呼ぶかを意識しなければならなくなる。

この問題を解決するために区分ごとに分けたクラスを同じ型として扱える様にインターフェースを定義すると異なるクラスを同じ型として扱うことができる。

interface Fee {
    Yen yen();
    String label();
}

class AdultFee implements Fee {
    Yen yen() {
        return new Yen(100);
    }

    String label() {
        return "大人";
    }
}
// ChildFee,SeniorFeeクラスも同様にFeeインターフェースを実装する

インターフェースを使うと呼び出す側のコードはif文を使うことなく区分を分けることができる

   Fee fee;

    Charge(Fee fee) {
        this.fee = fee;
        // Feeインターフェースを実装しているAdultFee型, ChildFee型, SeniorFee型のどれでも良い
    }

    Yen yen() {
        return fee.yen();
    }
}

この様にインターフェースと区分ごとの専用クラスを組み合わせて、区分ごとの異なるクラスのオブジェクトを「同じ型」として扱う仕組みを多態という。

列挙型(enum)を使うと区分ごとの一覧を分かりやすくできる

多態は区分ごとのロジックを整理する上では便利だが、一覧が分かりにくくなるという問題がある。

しかしJavaの列挙型(enum)を使うと区分ごとの一覧を明示的に記述できる。

enum FeeType {
    adult(new AdultFee()),
    child(new ChildFee()),
    senior(new SeniorFee());

    private Fee fee;

    private FeeType(Fee fee) {
        this.fee = fee;
    }

    Yen yen() {
        return fee.yen();
    }
}

// 区分名から料金を取得する
Yen feeFor(String feeTypeName) {
    FeeType feeType = FeeType.velueOf(feeTypeName);

    return feeType.yen();
}

上記の様に多態と列挙型を組み合わせることでif文を使わずに異なる区分の料金を取得することができる。

なお列挙型を使って、区分ごとのロジックを分かりやすく整理する方法を区分オブジェクトと呼ぶ。