いまさらリーダブルコードまとめ (第8章)


はじめに

チーム内リーダブルコード輪読会の第8章。
前回 第7章はこちら

第8章 巨大な式を分割する

どんな内容?

  • 巨大な式は飲み込みやすい(ぱっと分かりやすい)サイズに分割しよう
  • コードを飲み込みやすくするための主な手法
    • 説明変数、要約変数の利用
    • ド・モルガンの法則の利用
  • 他の人が見ても分かりやすい式にすることも大事

読んだ感想

新たな改修やリファクタリングを行う際に、
ついつい巨大化してしまったクラスやメソッドを意味にある処理毎などに分割する。というのは行っていたものの、
では具体的にどんな方法で分割していくのが良いのか、といった点については「意識的」に行えていたものもある一方で、
こんな方法・やり方もあったのか、何となくやっていた方法だけど、この対応もアリなんだなといったことなど、
個人的には色々な気づきを得られた章だった。

この章で上げられていることなどを担当しているプロダクトのケース、サイズに落とし込んで考えることで
新たな発見や改善点が見えてくるのでは?と考えさせられる章でした◎

例を挙げて議論してみよう

営業時間内に特定の処理を実施するケース(要約変数の例)

ex.8-1
static Long currentTime = LocalTime.now().getLong(ChronoField.HOUR_OF_DAY);
static final boolean OPENING_TIME = (currentTime >= 10 && currentTime < 21);
if (OPENING_TIME) {
    // 営業時間内にのみ実施される処理を実行
}
  • マジックナンバーを「OPEN_TIME」、「CLOSE_TIME」などの定数に置き換えるの手だが、1変数にまとめることで何をあらわしているのか分かりやすい

経過日数が特定の条件に一致するかどうかの判定(短絡評価の悪用の例)

ex.8-2-1
static final long UNLIMITED = 99L;

long passedDayCount = ChronoUnit.DAYS.between(fromDate, toDate) ;
long limitDayCount  = shop.getLimitDayCount();

if (limitDayCount != UNLIMITED && (limitDayCount >= 10 && limitDayCount < 30)) {
  //特定の処理
}
ex.8-2-2
static final long UNLIMITED = 99L;

long passedDayCount = ChronoUnit.DAYS.between(fromDate, toDate) ;
long limitDayCount  = shop.getLimitDayCount();

if (limitDayCount != UNLIMITED ) {
    if (limitDayCount >= 10 && limitDayCount < 30)) {
        //特定の処理
    }
}
  • 1行でざっと書くよりも「UNLIMITED ではない」ことを評価した上で、「limitDayCount が10以上30未満」と同じような判定条件ごとに行を分けて書くことで理解しやすくなる

式を簡潔にする方法の例(JavaでEnumを利用する)

ex.8-3-1(Main)
    public static void main(String[] args) {
        String shopName = "Test Mart";
        System.out.println("getGroupName_useEnum: " + getGroupName_useEnum(shopName));
        System.out.println("getGroupName_useIf: " + getGroupName_useIf(shopName));
    }

    private static String getGroupName_useEnum(String shopName) {
        String groupName = GroupShopEnum.FindGroupEnum(shopName).getGroupName();
        return groupName;
    }

    private static String getGroupName_useIf(String shopName) {
        String groupName = "No Group";
        if (shopName.equals("Test Mart") || shopName.equals("Test Shop") || shopName.equals("Test Store")) {
            groupName = "Test";
        } else if (shopName.equals("Hoge Mart") || shopName.equals("Hoge Shop") || shopName.equals("Hoge Store")) {
            groupName = "Hoge";
        }
        return groupName;
    }
ex.8-3-1(Enum)
public enum GroupShopEnum {

    TEST_GROUP_1("Test Mart",   "Test"),
    TEST_GROUP_2("Test Shop",  "Test"),
    TEST_GROUP_3("Test Store",  "Test"),
    HOGE_GROUP_1("Hoge Mart",  "Hoge"),
    HOGE_GROUP_2("Hoge Shop",  "Hoge"),
    HOGE_GROUP_3("Hoge Store", "Hoge"),
    DEFAULT("", "No Group");

    private String shopName;
    private String groupName;

    GroupShopEnum(String shopName, String groupName ){
        this.shopName =shopName;
        this.groupName =groupName;
    }

    public static GroupShopEnum FindGroupEnum(String shopName) {
        return Arrays.stream(values())
             .filter(v -> shopName.equals(v.shopName))
             .findFirst()
             .orElse(GroupShopEnum.DEFAULT);
    }

    public String getGroupName() {
        return groupName;
    }
}
  • 分岐が複数条件・パターンに分かれるときはEnumを活用して簡潔な処理にすることもできそう