iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Software Development

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

Day 06|召喚一個函式要念十句咒語:過長參數列表 (Long Parameter List)

  • 分享至 

  • xImage
  •  

文藝復興的畫室接委託案,客戶上門通常不會空手講幾句就走

畫多大、畫誰、用什麼背景、木板還是畫布、要不要鑲金、什麼時候要
每一項都得講清楚

講一次可能還好
但如果每一次跟畫室的人溝通,都要把這七八項條件重新覆誦一遍,講到第五次,順序就開始亂了

先兌現昨天的承諾

Day 05 留了一個尾巴:

private decimal CalculatePrice(Product product, int qty, CustomerTier tier, string couponCode)

四個參數,勉強還讀得出來

但真正該被拿出來鞭的,是 Process 本人

回頭看 Day 01 一路傳承下來的簽名:

七個參數,看了頭就開始痛了

public decimal Process(int customerId, int productId, int qty,
    string customerType, string couponCode, bool sendEmail, bool sendSms)

念咒語式的呼叫

實際呼叫這個方法,長這樣:

processor.Process(1001, 2002, 3, "VIP", "WELCOME10", true, false);

這一行,沒有人看得懂

  • 1001 是客戶編號還是商品編號?
  • true 是要發 email,還是要發簡訊?
  • false 又是哪一個?

想確定答案,只有一個辦法:跳回方法定義,從頭數第幾個參數對應第幾個位置
這跟背誦咒語的順序,沒有本質上的差別
背錯一個字,結果完全不同,而且編譯器不會提醒你

兩個相鄰的 bool 參數,sendEmailsendSms,順序一旦調換傳錯
程式照樣能編譯、能執行,只是簡訊變成信件,信件變成簡訊,沒有任何錯誤訊息告訴你哪裡不對

為什麼參數會越疊越多

沒有人一開始就想寫七個參數的方法

過程通常是這樣:

  1. 一開始只是 Process(customerId, productId, qty),三個參數,很乾淨
  2. 「要區分 VIP 折扣」-> 加一個 customerType
  3. 「要支援優惠碼」-> 加一個 couponCode
  4. 「客戶想收到通知」-> 加兩個 bool,一個管 email、一個管簡訊

每一次加一個,理由都很合理
加總起來,變成一句要背誦七個位置的咒語

幫委託開一張正式的單子

畫室後來想通了:與其每次口頭覆誦七八項條件,不如做一張「委託單」,把所有規格寫在上面

客戶只要填好單子交過去,畫室要用哪個欄位,自己去單子上找,不必再靠記順序

換成程式碼,這是引入參數物件 (Introduce Parameter Object)

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 的簽名,變成只接收一張「委託單」:

public decimal Process(OrderRequest request)
{
    var (customer, product) = LoadAndValidate(request.CustomerId, request.ProductId, request.Qty);
    decimal price = CalculatePrice(product, request.Qty, request.Tier, request.CouponCode);

    DeductStock(product, request.Qty);
    LogOrder(request.CustomerId, request.ProductId, request.Qty, price);
    NotifyCustomer(customer, request.SendEmail, request.SendSms);

    return price;
}

呼叫端變成:

processor.Process(new OrderRequest
{
    CustomerId = 1001,
    ProductId = 2002,
    Qty = 3,
    Tier = CustomerTier.Vip,
    CouponCode = "WELCOME10",
    SendEmail = true,
    SendSms = false
});

每個值都掛著自己的名字,不必再靠位置背誦。

半年後想加一個「是否要開發票」,只要在 OrderRequest 多一個屬性
Process 的簽名完全不用改,所有呼叫端也不用跟著改

不是每個方法都要塞進物件

參數多,不一定都要立刻包成物件。判斷的重點是:

  • 呼叫這個方法時,我需不需要跳回去看定義才能確定參數順序?
  • 這些參數裡,有沒有兩個以上型別相同,容易被傳錯位置(像那兩個 bool)?
  • 這組參數,是不是本來就代表一個概念(像「這張委託單」),只是還沒有名字?

只要答案讓你猶豫,這個方法大概已經在念咒語了

自我檢查清單

  1. 這個方法的參數,超過三個了嗎?
  2. 呼叫這個方法時,我需要跳回定義去確認參數順序嗎?
  3. 有沒有兩個以上參數型別相同,容易被誤傳位置?
  4. 這組參數合起來,是不是其實代表一個還沒有名字的概念?
  5. 如果之後要多加一個條件,是不是又要跟著改一次簽名、改一次所有呼叫端?

明日預告

OrderRequest 裡的 CustomerIdProductIdQty,看起來已經很乾淨了
但如果你去翻其他方法,會發現這三個值,總是結伴出現,卻始終沒有一個共同的名字

模組一最後一站:資料泥團(Data Clumps)


上一篇
Day 05|沒有模具的工地:原始型別執念 (Primitive Obsession)
下一篇
Day 07|總是結伴出現卻沒有名字的三兄弟:資料泥團 (Data Clumps)
系列文
文藝復興:這段程式碼,好像有點味道9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言