iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Software Development

消除你程式碼的臭味系列 第 19

Day 19- 參數:少即是多,避免傳布林參數

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250903/20124462P1N8QjGguI.png

消除你程式碼的臭味 Day 19- 參數:少即是多,避免傳布林參數

參數越多,理解成本越高。
問個簡單的問題:當你看到一行程式碼 createUser('Bob', 'bob@h.com', 42, true, false),你知道那兩個 truefalse 是什麼意思嗎?

不知道,沒有人知道,必須要回去翻原始碼,生命又消逝了好幾秒好幾分鐘。
一個好的函式,應該像一個設計良好的網頁表單,每個輸入框前面都有清晰的標籤。

用物件收斂參數,避免傳布林參數改流程。

經典案例:傳布林參數讓流程分叉

一個 truefalse 的參數,實際上是在告訴世界:「這個函式偷偷地在做兩件完全不同的事,具體做哪件,取決於你傳入的這個神秘開關。」

// 🔴 臭味:這是一個糟糕的 API。它難以閱讀、容易出錯、無法擴展。
function createUser(name, email, age, isAdmin, sendWelcomeEmail) { /* ... */ }

// 呼叫點就是一串密碼。isAdmin 在前還是 sendWelcomeEmail 在前?誰記得住?
createUser('Eve', 'eve@h.com', 30, false, true);

// 如果順序錯了,bug 就產生了,而且很難被發現。
createUser('Mallory', 'm@h.com', 99, true, false); 
// 原本想發送郵件,結果變成了管理員
// 這個函式身兼數職,而且還不寫在它的名片上。

https://ithelp.ithome.com.tw/upload/images/20250921/201244623EvoIyNiRt.png

這違反了單一職責原則 (Single Responsibility Principle,SRP)
一個函式應該只做一件事,並把它做好。

當你用一個布林參數來切換兩種行為時,代表這個函式至少做了兩件事。
這就是臭味的來源。

用物件與預設值:把「密碼」變回「表單」

改用一個物件,讓每個參數都有自己的名字

// 🟢 好品味:這是一個自我說明的、穩定的 API。
// 函式只接受一個物件,參數就像有了標籤,清晰易懂。

/**
 * 主要函式:建立使用者。
 */
function createUser({ name, email, age, role = 'member' }) {
  // ✅ 具名參數 : 透過解構賦值 { name, ... },參數的順序不再重要。
  // ✅ 預設值 : `role = 'member'` 提供了穩定的預設行為。
  // ✅ 優化模型 : 用 'role' (字串) 取代 'isAdmin' (布林),未來更有擴充性。
  
  console.log(`  [主要] 正在建立使用者 ${name},角色為 ${role}...`);
  return { name, email, age, role };
}

/**
 * 獨立職責:寄送 Email 的行為被分離出來,專注做一件事。
 */
function sendWelcomeEmail(user) {
  console.log(`  [行為] 正在寄送歡迎信給 ${user.email}...`);
}

/**
 * 特殊流程:當行為模式顯著不同時,提供一個新的、命名清晰的函式。
 * 這遠比在 createUser 內部用 if (isAdmin) ... else ... 要好。
 */
function createAdminUser({ name, email, age }) {
  console.log("-> 開始「建立管理員」的特殊流程...");
  
  // ✅流程組合: 重複利用 `createUser` 的核心功能,並添加額外步驟。
  const adminUser = createUser({ name, email, age, role: 'admin' });
  
  console.log(`  [特殊] 正在為管理員 ${adminUser.name} 設定特殊權限...`);
  console.log("-> 管理員流程結束。");
  return adminUser;
}


// --- 實際呼叫點 (The Call Site) ---

// 情境一:建立普通使用者
// ✅可讀性高: 呼叫點像在填寫一份有標籤的表單,意圖一目了然。
const user1 = createUser({
  name: 'Eve',
  email: 'eve@example.com',
  age: 30
});
// ✅【行為組合】: 可以彈性地組合 `createUser` 和 `sendWelcomeEmail`。
sendWelcomeEmail(user1);


console.log("\n情境二:建立管理員");
// ✅意圖明確: 直接呼叫 `createAdminUser`,程式碼的意圖非常清楚。
const admin1 = createAdminUser({
  name: 'Mallory',
  email: 'mallory.admin@example.com',
  age: 42
});
// (根據需求,管理員可能不需要寄送一般歡迎信,行為可以自由組合)

https://ithelp.ithome.com.tw/upload/images/20250921/20124462UpLXESSySi.png

  • 它自我說明: name: 'Eve' 這樣的寫法,程式碼自己就解釋了參數的意義。

  • 它穩定且可擴展: 參數的順序無關緊要。未來如果需要增加新的可選參數,直接在物件裡增加屬性就行,完全不會破壞任何舊的呼叫程式碼。

  • 它強迫你做出更好的設計: 把布林參數 isAdmin 改成 role,優化資料模型。
    sendWelcomeEmail 這個行為從 createUser 裡分離出來,讓每個函式只做一件事,這才對。

除臭的設計原則

  • 三個以上參數就該收斂成物件。
  • 避免位置參數造成誤用,使用具名參數(物件解構)。
  • 杜絕布林開關 true/false。一個開關代表「兩種行為/職責」,請把函式一分為二。

物件參數的做法

  • 以預設值提供穩定行為,呼叫端只傳需要改的那一部分。
  • 把驗證寫在入口,格式化資料後再往內層傳遞。

檢查清單

  1. 參數超過三個嗎?
  2. 是否有布林參數在控制流程?
  3. 能否把參數收斂成物件?
  4. 未來加新參數時,是否會破壞所有舊的呼叫?是否提供合適的預設值?

今日重點

  • 一個行為,一個函式。 別用布林值當開關。
  • 一組參數,一個物件。 拋棄順序,擁抱命名。
  • 一個介面,一份信賴。 讓呼叫的意圖清晰可見。

停止折磨你的同事和未來的你自己。
開始為你的函式設計真正好用的介面。


上一篇
Day 18- 狀態管理:用資料定義流程
系列文
消除你程式碼的臭味19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言