一個迴圈的本質工作,就是對一系列相似的東西做同樣一件事情。
當你在迴圈裡面塞滿了邊界判斷和特殊處理,邏輯就會變亂,也更容易出錯。
先做好準備工作,將所有例外、邊界情況處理好。
然後,讓迴圈專心、單純地執行「主要工作」。
把特殊處理塞在迴圈裡。
// 🔴 臭味道:把設置、清理、主要心邏輯全混在一起
function renderList(items) {
let output = '';
for (let i = 0; i < items.length; i++) {
// 這些檢查跟迴圈的核心任務有什麼關係?沒有。
if (!Array.isArray(items)) return '';
if (items[i] == null) continue;
// 每次迭代都要問「我是不是第一個?」
if (i === 0) output += 'START\n';
output += `- ${items[i]}\n`; // 這才是迴圈真正該做的事
// 每次迭代也都要問「我是不是最後一個?」
if (i === items.length - 1) output += 'END\n';
}
return output;
}
混淆職責: 這段程式碼把「邊界處理」(START
/END
)和「資料處理」(- ${items[i]}
)攪和在一起。一個函式、一個迴圈,應該只做一件事。
極度低效: i === 0
的判斷只在第一次為真,但它會在迴圈的一百萬個元素中,執行一百萬次。i === items.length - 1
同樣如此。浪費 CPU 時間去做重複且無意義的檢查。
可讀性災難: 真正的主要邏輯 output += ...
被一堆 if
包圍,難以閱讀。
除臭第一步就是知道如何分離問題。
先處理好所有邊界情況和特殊輸入,然後用一個乾淨、簡單的迴圈來處理主要工作。
建立清晰的資料處理管道,將整個過程分解成幾個獨立的步驟:驗證 -> 清理 -> 處理 -> 組合。
// 🟢 好品味:先處理邊界,再專心處理主要工作
function renderList(items) {
// 1. 保護式檢查 (Guard Clause):把所有不要的輸入先擋掉
if (!Array.isArray(items) || items.length === 0) {
return '';
}
// 2. 準備資料:先把 null 過濾掉,只留下我們要處理的
const cleanItems = items.filter(x => x != null);
if (cleanItems.length === 0) {
return '';
}
// 3. 主要工作:迴圈只做一件事
const body = cleanItems.map(x => `- ${x}`).join('\n');
// 4. 組合結果:把邊界和主要工作拼起來
return `START\n${body}\nEND`;
}
消除特殊情況: 透過 filter
,迴圈主體 map
根本不需要知道 null
的存在。它處理的每一個元素都是「正常情況」。這就是重點。
清晰的資料流 pipe: items
-> cleanItems
-> body
-> final result
。每一步都只做一件事。你可以輕易地測試任何一個環節。
關注點分離:每個區塊只做一件事。修改驗證邏輯不會動到核心處理,反之亦然。
.filter().map() 寫法會產生暫存陣列,因此會多用一點記憶體。
相比之下,傳統 for 迴圈則最節省記憶體。
那該如何選擇?結論很簡單:在絕大多數情況下,都應該優先選擇可讀性最高的寫法。
只有在遇到效能瓶頸極端情況才考慮另一種方案。
// 🔴 壞味道:在迴圈裡重複做一個不會改變的決定
function applyDiscount(items, level) {
const result = [];
for (const item of items) {
// `level` 在迴圈中從未改變,但這個判斷卻執行了 N 次。
if (level === 'vip') {
result.push(item.price * 0.8);
} else if (level === 'member') {
result.push(item.price * 0.9);
} else {
result.push(item.price);
}
}
return result;
}
把不變的判斷提到迴圈外,讓主體邏輯一行完成。折扣率的決策與迴圈的迭代無關。
// 🟢 好品味:先把「決策」提到迴圈外,只做一次
function applyDiscount(items, level) {
// 1. 提取不變量:先根據 level 決定折扣率
const rate = level === 'vip' ? 0.8 : level === 'member' ? 0.9 : 1;
// 2. 應用決策:讓迴圈專心執行計算
return items.map(item => item.price * rate);
}
這個改進不只效能好,而且更清楚。
迴圈的意圖變成單純的「將每個元素的價格乘以一個比率」,而不是「為每個元素檢查會員等級再決定折扣」。
衛兵先行: 在迴圈開始前,用保護式子句處理掉所有無效輸入(null
、空陣列等)。
清理資料: 在迴圈開始前,先把資料預處理好。過濾掉無效項,讓進入迴圈的都是乾淨、格式統一的資料。
提取不變量: 任何在迴圈內不會改變的計算或判斷,都提到迴圈外面。
迴圈要笨: 讓迴圈主體只做最核心、重複性的那件事。
事後處理: 首、尾元素的特殊邏輯,應該在迴圈執行前和執行後處理。
一個好的迴圈,它的主體應該只處理第 N
個元素,而不是為第 0
個或第 N-1
個元素增加特殊邏輯。
好的程式碼不是沒有特殊情況,而是懂得如何聰明地「隔離」它們,讓主要情況與邏輯保持單純與穩定。