函式的第一守則是短小,第二守則是更短小。但「短」只是表象,真正的判準是只做一件事、把它做好、而且只做這件事。一個函式若能被拆成「初始化區、處理區、收尾區」這種段落,它就已經做了不只一件事。
🧠 Why It Matters
- 只做一件事 = 單一職責:函式該對應到一個、且僅一個變化的理由。混進第二件事,任何一邊的需求變動都會打到同一個函式。
- 單一抽象層次:函式內的語句要維持在同一層抽象——別讓「高階流程」和「低階位元操作」並排。混層會逼讀者在不同高度之間反覆跳躍。
- 內聚性直接關聯缺陷率:Code Complete 引研究指出,高內聚(只做一件事)的子程式有 50% 完全無缺陷,低內聚的只有 18%;耦合/內聚比最差的子程式,錯誤數是最好者的 7 倍、修復成本 20 倍。
- 「只做一件事」是可驗證的:如果你還能從這個函式裡抽出另一個名字不只是重述實作的函式,那它就做了不只一件事。
NOTE
內聚性的理想是功能內聚——sin()、getCustomerName()、eraseFile() 各只做一件事。要警惕的是邏輯內聚(塞進多個不相關操作、靠傳旗標選一個執行)與巧合內聚(操作之間毫無關聯),這兩者是重新設計的訊號。
⚖️ Case Study
前 — 一個函式身兼三職
// 同時算總和、找極值、算平均:三件事、三個變化理由
function processScores(scores) {
let total = 0;
let min = Infinity;
let max = -Infinity;
for (const s of scores) {
total += s;
if (s < min) min = s;
if (s > max) max = s;
}
return { average: total / scores.length, min, max };
}想改「平均的算法」得讀懂極值邏輯,想改「極值」得繞過總和——三件事糾纏在同一個迴圈裡。
後 — 各司其職,主流程讀起來像摘要
function average(scores) {
return scores.reduce((sum, s) => sum + s, 0) / scores.length;
}
function minScore(scores) {
return Math.min(...scores);
}
function maxScore(scores) {
return Math.max(...scores);
}
// 使用端讀起來像目錄,每個名字是一次「要不要深入」的選擇
const stats = {
average: average(scores),
min: minScore(scores),
max: maxScore(scores),
};每個函式只有一個變化理由;改任一個都不會波及其他。這正是 Extract Function 把「意圖」與「實作」分層的結果。
TIP
別因為「只有兩三行」就不肯抽子程式。小操作往往會長大——日後要加錯誤處理或邊界檢查時,集中在一處只改一次,散在十幾處就得改十幾次。一行 points = deviceUnitsToPoints(deviceUnits) 也比展開的公式好讀。
🔑 Takeaways
- 「小」是表象,「只做一件事」才是判準:還能抽出一個名字不只是重述實作的函式 → 它做了不只一件事。
- 維持單一抽象層次,別讓高階流程與低階細節並排。
- 內聚性可量化地降低缺陷:追求功能內聚,警惕邏輯/巧合內聚。
- 再小的操作也值得命名成子程式——它會長大,集中管理最划算。
- 標準手法是 Extract Function;反面味道是 長函式。
No notes yet — jot your takeaways or Q&A here.