參數是函式與讀者之間的溝通成本。每多一個參數,讀者要理解的細節就多一份、測試要覆蓋的組合就多一輪。理想的參數數量是零,其次是一,其次是二;到了三個就該停下來問:這些參數是不是在告訴我,這裡藏著一個還沒被命名的概念?
🧠 Why It's a Problem
- 參數是理解障礙:零參數(niladic)最好,單參數(monadic)常見易懂,雙參數(dyadic)尚可但需記順序,三參數(triadic)應盡量避免,更多則不該出現。
- 順序記憶負擔:
dyadic以上就要求讀者記住參數順序——copy(source, destination)還是copy(destination, source)?順序一錯,bug 就悄悄成立。 - 旗標參數是壞味道:傳一個 boolean 進去、函式內部再
if分岔,等於公然宣告「我做了兩件事」——這違反 只做一件事。把它拆成兩個函式。 - 輸出型參數造成認知斷層:讀者預期參數是輸入。若參數被拿來帶回結果(改內容後回傳),會打斷「輸入從這裡進、結果從回傳值出」的直覺。用回傳值,不要用輸出參數。
TIP
當函式看來需要三個以上參數,通常代表其中一些應該被包成一個物件。makeCircle(x, y, radius) → makeCircle(center, radius):把 x, y 包成 Point 不只是少一個參數,而是替一個本來就存在的概念命名。
⚖️ Case Study
前 — 參數爆炸 + 旗標參數
// 五個參數、順序難記,還有一個 boolean 旗標在內部分岔
function createUser(name, email, isAdmin, sendWelcome, timezone) {
const user = { name, email, role: isAdmin ? "admin" : "member", timezone };
if (sendWelcome) mailer.sendWelcome(user);
return user;
}
// 呼叫端:true / false 是什麼意思?順序對嗎?
createUser("Bob", "bob@x.com", false, true, "Asia/Taipei");isAdmin 讓函式做兩種角色、sendWelcome 讓它可能寄信也可能不寄——旗標把兩三個函式硬塞成一個。
後 — 參數物件 + 拆掉旗標
// 相關參數包成一個有名字的概念;旗標拆成兩個意圖清楚的函式
function createUser({ name, email, role = "member", timezone }) {
return { name, email, role, timezone };
}
function createAdmin(profile) {
return createUser({ ...profile, role: "admin" });
}
// 呼叫端自我說明,順序不再是負擔
const user = createUser({ name: "Bob", email: "bob@x.com", timezone: "Asia/Taipei" });
mailer.sendWelcome(user); // 「要不要寄信」由呼叫端明確決定,不藏在旗標裡參數物件讓呼叫端自我說明、順序無關;把 isAdmin/sendWelcome 的分岔移出函式,每條路徑都只做一件事。
WARNING
Code Complete 補充:輸入、修改、輸出參數若混用,至少讓排列順序有慣例(輸入在前、輸出在後)並保持全專案一致;但更好的做法仍是盡量避免修改/輸出參數,改用回傳值。
🔑 Takeaways
- 參數越少越好:0 > 1 > 2;到 3 個就該懷疑藏了未命名的概念。
- 旗標參數(boolean)幾乎總是「做兩件事」的訊號——拆成兩個函式。
- 三個以上參數常代表該包成一個參數物件,順帶替一個概念命名。
- 避免輸出型參數:結果走回傳值,別讓參數兼作輸出。
No notes yet — jot your takeaways or Q&A here.