iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Software Development

文藝復興:這段程式碼,好像有點味道系列 第 5

Day 05|沒有模具的工地:原始型別執念 (Primitive Obsession)

  • 分享至 

  • xImage
  •  

圓頂的曲線,不是每一位工匠憑感覺砌出來的
布魯內雷斯基替不同高度的每一圈磚,都準備了專屬的木製模具(centina)

工匠不需要自己判斷「這一圈該彎多少」,只要照著模具的弧度砌,整座圓頂的曲線就會維持一致

模具,把「正確的形狀」這件事,變成一個看得到、摸得到、不會被誤解的標準

一句話,藏著一個沒有模具的判斷

回到 Day 03 拆出來的 CalculatePrice

private decimal CalculatePrice(Product product, int qty, string customerType, string couponCode)
{
    decimal price = product.Price * qty;

    if (customerType == "VIP")
        price *= 0.9m;
    else if (customerType == "REGULAR" && couponCode == "WELCOME10")
        price *= 0.95m;

    return price;
}

customerType 是一個 string

它看起來只是資料,其實藏著一整套業務規則:客戶只能是「VIP」或「REGULAR」,不能是別的

但僅靠 string 沒辦法替你守住這條規則

沒有模具,會發生什麼事

想像三個地方,各自要判斷客戶是不是 VIP:

if (customerType == "VIP") { ... }
if (customerType == "Vip") { ... }
if (customerType == "vip") { ... }

三行看起來都合理,但只有第一行會生效

  • 打錯一個字母,編譯器不會有意見
  • 這種錯誤通常不會在開發階段被發現,會一路帶到 Production,悄悄地讓某些 VIP 客戶少了折扣
  • 等到有人發現「怎麼有客戶抱怨沒打折」,才回頭追查,往往已經過了好一段時間

每個工匠憑感覺砌出來的弧度,看起來都「差不多對」——直到某一圈的誤差被下一圈放大,整座圓頂開始歪斜

這就是原始型別執念(Primitive Obsession)的代價:
規則沒有一個固定的形狀,只能靠每個人各自記住、各自猜

幫規則做一個模具

解法的核心思想很直接:把「客戶只能是 VIP 或 REGULAR」這條規則,做成一個真正的型別,而不是繼續用字串硬撐。

public enum CustomerTier
{
    Regular,
    Vip
}

方法簽名跟著換掉:

private decimal CalculatePrice(Product product, int qty, CustomerTier tier, string couponCode)
{
    decimal price = product.Price * qty;

    if (tier == CustomerTier.Vip)
        price *= 0.9m;
    else if (tier == CustomerTier.Regular && couponCode == "WELCOME10")
        price *= 0.95m;

    return price;
}

現在再打錯字,編譯器會直接擋下來CustomerTier.VIP 這種寫法根本不會通過編譯,不必等到 Production 才發現問題

模具不只管形狀,也管規則

CustomerTier 解決了「打錯字」的問題,但 couponCode 還是一個裸的 string

如果優惠碼還有格式規則(例如一律轉大寫、不能有空白),這些規則該放在哪裡?

答案不是散落在每個用到它的地方,而是封裝進一個專屬的小物件

public readonly record struct CouponCode
{
    public string Value { get; }

    public CouponCode(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("優惠碼不可為空白");

        Value = value.Trim().ToUpperInvariant();
    }
}

CouponCode 保證了一件事:只要它存在,它就一定是有效格式

呼叫端不用重複寫「先 trim、再轉大寫、再檢查空白」——這些規則只活在一個地方

這正是 Day 01 提過的 Primitive Obsession 診斷:

用字串代表狀態,打錯字不會編譯錯誤,只會在 Production 悄悄算錯

模具做出來之後,這句話就不再成立了

什麼時候該幫資料做模具

不是每一個 stringint 都要換成物件,判斷的重點是:

  • 這個原始型別,背後是不是藏著只有幾種合法值的規則(像 VIP / REGULAR)?
  • 好幾個地方,是不是都各自重複寫著同一段驗證或格式化邏輯?
  • 這個值如果傳錯位置(例如把 Email 傳進電話號碼的參數),編譯器攔不攔得下來?

只要有一題的答案讓你不安,這個原始型別大概該有自己的模具了

自我檢查清單

  1. 程式碼裡有沒有用字串或數字,代表某種只有幾種合法值的「類型」或「狀態」?
  2. 這個值的驗證或格式化邏輯,是不是在好幾個地方各自重複寫過?
  3. 如果把這個值傳錯位置,編譯器擋不擋得下來?
  4. 這個原始型別背後,是不是其實藏著一組業務規則?
  5. 如果幫它做一個小物件,呼叫端的程式碼會不會變得更清楚?

明日預告

明天,我們回頭看 CalculatePrice(Product product, int qty, CustomerTier tier, string couponCode) 這串參數本身——當一個方法的參數多到要背誦順序,那又是另一種警訊

模組一第四站:過長參數列表(Long Parameter List)


上一篇
Day 04|身兼一切的工地主任:巨大類別 (Large Class)
下一篇
Day 06|召喚一個函式要念十句咒語:過長參數列表 (Long Parameter List)
系列文
文藝復興:這段程式碼,好像有點味道9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言