函式越長,要讀懂它、安全地改它就越難。長度本身不是病,「為了理解它必須在腦中同時裝下太多細節」才是。最該被抽出來的,往往就是那些你得停下來寫一行註解解釋的片段。
🧠 Why It's a Problem
- 認知負荷:讀者得一次追蹤所有區域變數、分支與副作用,無法「看名字就略過細節」。
- 缺乏命名的中介層:一段 10 行的計算如果沒有名字,意圖只能靠讀者逆向推導。
- 難以重用與測試:邏輯和它的上下文糾纏在一起,想單獨測一小段幾乎不可能。
- 變化的震央:長函式通常身兼數職,於是任何需求變動都打在同一個地方(發散式變化)。
NOTE
判準不是「行數超過 N 就是壞」,而是 Fowler 的提問:這段程式碼在做什麼? 只要答案需要一個「而且…」,它大概就承擔了不只一件事。
⚖️ Refactoring
核心解法幾乎永遠是 Extract Function:把「做什麼(意圖)」和「怎麼做(細節)」分到不同層。
前 — 一個函式做完所有事
function printOwing(invoice) {
let outstanding = 0;
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
for (const o of invoice.orders) {
outstanding += o.amount;
}
const today = new Date();
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}後 — 用具名函式描述意圖
function printOwing(invoice) {
printBanner();
const outstanding = calculateOutstanding(invoice);
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
function printBanner() {
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
}
function calculateOutstanding(invoice) {
return invoice.orders.reduce((sum, o) => sum + o.amount, 0);
}
function recordDueDate(invoice) {
const today = new Date();
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}printOwing 現在讀起來像一段摘要:四個步驟、四個名字。細節各自退到自己的函式裡,需要時才展開。
🔑 Takeaways
- 長函式的真正成本是認知負荷,不是行數;以「需不需要寫註解解釋這段」當抽取訊號。
- 標準解是 Extract Function,把「意圖」與「實作細節」分層。
- 抽取後上層函式讀起來像目錄,每個名字是一次「要不要深入」的選擇。
- 若抽出的函式參數爆炸,往往代表還藏著別的 smell(資料泥團、feature envy)值得再追。
No notes yet — jot your takeaways or Q&A here.