工匠採買磚頭,從來不是單獨算一塊一塊的價錢。
磚的種類、數量、要送去哪一圈。 這三件事,永遠是一起被記在同一張採購單上
沒有人會把「數量」單獨記在一本帳,「種類」記在另一本帳,用的時候再想辦法對起來
因為這三個資訊,早就是同一件事的三個面向
Day 06 把七個參數包進了 OrderRequest:
public record OrderRequest
{
public int CustomerId { get; init; }
public int ProductId { get; init; }
public int Qty { get; init; }
public CustomerTier Tier { get; init; }
public string CouponCode { get; init; }
public bool SendEmail { get; init; }
public bool SendSms { get; init; }
}
問題解決了嗎? 只解決了一半
去翻 Process 內部呼叫的每一個方法:
private (Customer, Product) LoadAndValidate(int customerId, int productId, int qty) { ... }
private decimal CalculatePrice(Product product, int qty, CustomerTier tier, string couponCode) { ... }
private void DeductStock(Product product, int qty) { ... }
private void LogOrder(int customerId, int productId, int qty, decimal price) { ... }
productId 和 qty,在四個方法裡,出現了四次
有一個簡單的試驗方法:試著把 qty 從某一個方法簽名裡拿掉
private void DeductStock(Product product) { ... }
DeductStock 立刻失去意義。 扣庫存扣多少?沒有 qty,這個方法根本做不了事
這正是資料泥團的辨識訣竅:拆掉其中一個變數,如果剩下的變數瞬間變得不完整,就代表它們從一開始就該被綁在一起
productId 和 qty,其實一直在描述同一件事:「這張訂單,要買哪個商品、買幾件」
只是這件事,一直沒有一個名字
沒有名字的資料泥團,代價不只是「參數看起來很像」:
LoadAndValidate?還是 CalculatePrice?還是兩個都要加?把 productId 和 qty,正式封裝成一個屬於自己的類別:
public record OrderLine
{
public int ProductId { get; }
public int Qty { get; }
public OrderLine(int productId, int qty)
{
if (qty <= 0)
throw new ArgumentException("數量必須大於零");
ProductId = productId;
Qty = qty;
}
}
「數量必須大於零」這條規則,現在只活在一個地方。
四個原本各自散落的方法,跟著換成接收 OrderLine:
private (Customer, Product) LoadAndValidate(int customerId, OrderLine line) { ... }
private decimal CalculatePrice(Product product, OrderLine line, CustomerTier tier, string couponCode) { ... }
private void DeductStock(Product product, OrderLine line) { ... }
private void LogOrder(int customerId, OrderLine line, decimal price) { ... }
OrderRequest 也跟著整理,直接把 OrderLine 收進去,而不是繼續攤平成兩個獨立欄位:
public record OrderRequest
{
public int CustomerId { get; init; }
public OrderLine Line { get; init; }
public CustomerTier Tier { get; init; }
public string CouponCode { get; init; }
public bool SendEmail { get; init; }
public bool SendSms { get; init; }
}
四個方法簽名同時變短,而且「一次訂購要買什麼」這個概念,終於有了自己的名字和家
從 Day 03 到 Day 07,我們一路把 OrderProcessor 從一個 60 行、七參數、字串硬撐規則的方法,拆成:
ShopManager 上)string(Primitive Obsession)這五天處理的,都是同一件事:體積本身,不是重點;有沒有比例,才是重點
明天先不急著離開工地
Day 08 會退到廣場外,替模組一的五個警訊做一次驗收:當它們同時出現,該先修哪一個?
完成比例檢視後,Day 09 再進入模組二,拜訪達文西與物件導向的濫用者