いま「良いコード/悪いコードで学ぶ設計入門」を読んでる。今日はその途中で出てきたリファクタリングのサンプルで休日らしく息抜きに遊んでみたのでメモ。とても良い本なので読み終わったら感想を書こうと思ってる。
遊んだコード
おいといた
Commits · bufferings/20220506-refactoring · GitHub
元のコード
第14章にあるサンプル。これを書籍を参考にしながら、自分の好きな方法でリファクタリングする。
public class DeliveryManager { public static int deliveryCharge(List<Product> products) { int charge = 0; int totalPrice = 0; for (Product each : products) { totalPrice += each.price; } if (totalPrice < 2000) { charge = 500; } else { charge = 0; } return charge; } }
既存のメソッドに対してテストを書く
Add test · bufferings/20220506-refactoring@64c2a14 · GitHub
書籍では、先にリファクタリング先の構造を作ってからそれに対してテストを書いてるけど、僕は先に既存のメソッドに対してテストを書いてみたいなと思ったのでそうした。テストメソッド名は日本語でいいかな
class DeliveryManagerTest { @Test public void 商品の合計金額が2000円未満の場合_配送料は500円() { var products = List.of( new Product(1, "商品A", 500), new Product(2, "商品B", 1499) ); assertEquals(500, DeliveryManager.deliveryCharge(products)); } @Test public void 商品の合計金額が2000円以上の場合_配送料は無料() { var products = List.of( new Product(1, "商品A", 500), new Product(2, "商品B", 1500) ); assertEquals(0, DeliveryManager.deliveryCharge(products)); } }
ロジックを移動
Move logic · bufferings/20220506-refactoring@5863f31 · GitHub
テストができたので、それを Green にしたままロジックを DeliveryManager
から DeliveryCharge
に移動
public class DeliveryCharge { final int amount; public DeliveryCharge(ShoppingCart cart) { int charge = 0; int totalPrice = 0; for (Product each : cart.products) { totalPrice += each.price; } if (totalPrice < 2000) { charge = 500; } else { charge = 0; } this.amount = charge; } }
DeliveryManager
はこうなる
public static int deliveryCharge(List<Product> products) { var cart = new ShoppingCart(); for (var elem : products) { cart = cart.add(elem); } var charge = new DeliveryCharge(cart); return charge.amount; }
テスト対象を DeliveryManager
から DeliveryCharge
に変更
とりあえずこんな感じで
@Test public void 商品の合計金額が2000円未満の場合_配送料は500円() { var products = List.of( new Product(1, "商品A", 500), new Product(2, "商品B", 1499) ); var cart = new ShoppingCart(); for (var elem : products) { cart = cart.add(elem); } var charge = new DeliveryCharge(cart); assertEquals(500, charge.amount); }
DeliveryManager
を削除
Delete DeliveryManager · bufferings/20220506-refactoring@ca48d73 · GitHub
いらなくなったので削除
テストをリファクタリング
Refactor test · bufferings/20220506-refactoring@ecf4e3d · GitHub
さっき、とりあえずで動くようにしたテストを、ちゃんと書き直した
@Test public void 商品の合計金額が2000円未満の場合_配送料は500円() { var cart = new ShoppingCart(); cart = cart.add(new Product(1, "商品A", 500)); cart = cart.add(new Product(2, "商品B", 1499)); var charge = new DeliveryCharge(cart); assertEquals(500, charge.amount); }
合計金額の計算を ShoppingCart
に移動
書籍にも書いてあるとおりのリファクタリング
Move total price calculation to ShoppingCart · bufferings/20220506-refactoring@366eab5 · GitHub
Stream 使ってみた
public int totalPrice() { return products.stream().mapToInt(product -> product.price).sum(); }
DeliveryCharge
をリファクタリング
やっとメインのリファクタリングだね。これも書籍の通り。三項演算子はあんまり好きじゃないので普通に if で
Refactor DeliveryCharge logic · bufferings/20220506-refactoring@b0bf4c6 · GitHub
public class DeliveryCharge { private static final int CHARGE_FREE_THRESHOLD = 2000; private static final int PAY_CHARGE = 500; private static final int CHARGE_FREE = 0; final int amount; public DeliveryCharge(ShoppingCart cart) { int totalPrice = cart.totalPrice(); if (totalPrice < CHARGE_FREE_THRESHOLD) { amount = PAY_CHARGE; } else { amount = CHARGE_FREE; } } }
最後に自分の好みにリファクタリング
Refactor production code · bufferings/20220506-refactoring@7b527ae · GitHub
個人的にはフィールド直接アクセス好きじゃないのでアクセサメソッドをつけた
public class Product { private final int id; private final String name; private final int price; public Product(int id, String name, int price) { this.id = id; this.name = name; this.price = price; } public int id() { return id; } public String name() { return name; } public int price() { return price; } }
なんとなく DeliveryCharge
にファクトリーメソッドを作った
public class DeliveryCharge { private static final int CHARGE_FREE_THRESHOLD = 2000; private static final int PAY_CHARGE = 500; private static final int CHARGE_FREE = 0; public static DeliveryCharge from(ShoppingCart cart) { int totalPrice = cart.totalPrice(); if (fulfillChargeFreeCondition(totalPrice)) { return new DeliveryCharge(CHARGE_FREE); } else { return new DeliveryCharge(PAY_CHARGE); } } private static boolean fulfillChargeFreeCondition(int totalPrice) { return totalPrice >= CHARGE_FREE_THRESHOLD; } private final int amount; private DeliveryCharge(int amount) { this.amount = amount; } public int amount() { return amount; } }
あとはちょこっとテストを付け足して終わり
楽しかった
休日っぽくふわふわコード書いて楽しかった