很多人以為,程式設計的厲害在於演算法或是那些設計模式。
但還有一個更務實的概念一定要掌握。
Bad programmers worry about the code.
Good programmers worry about data structures and their relationships.
拙劣的工程師擔心程式碼,優秀的工程師關心資料結構與其間的關係。
一個工程師的層次,往往取決於他如何組織資料。
「好品味 Good Taste」,不是指用了多麼複雜的技巧或寫出花俏的程式碼,而是能從資料結構層面,看穿問題的本質,從根本上化繁為簡的直覺。
先假設我們有一個鏈結串列: 10 -> 20 -> 30 -> null
。
需要刪除某個節點。
🔴 臭味程式碼處理(10 行,帶 if 判斷)
這段程式碼的作者,恐懼所有可能的邊界情況(Edge Case)。
他害怕串列是空的,也害怕要刪除的恰好是頭節點。
於是,他用層層的 if
條件來保護自己。
// 充滿防禦性檢查的程式碼
function removeNode(head, targetValue) {
// 特殊情況 1: 害怕空串列
if (head === null) {
return null;
}
// 特殊情況 2: 害怕刪除的是頭節點
if (head.value === targetValue) {
return head.next;
}
// 一般情況:總算進入「正常」邏輯
let current = head;
while (current.next !== null) {
if (current.next.value === targetValue) {
current.next = current.next.next;
return head; // 操作完成,提前返回
}
current = current.next;
}
return head; // 找不到目標
}
這是典型的壞味道(Code Smell)。
三段風格迥異的邏輯,處理的卻是同一個核心問題,程式碼因此變得複雜而不必要。
把程式碼變成流程圖:
🟢 好品味的程式碼(4 行,無條件分支)
有品味的程式設計師不處理特殊情況,他們消滅特殊情況。
// 透過改變資料結構簡化邏輯
function removeNode(head, targetValue) {
// 建立一個 dummy 節點,它的 next 指向真正的 head
const dummy = { next: head };
let prev = dummy;
let current = head;
while (current !== null) {
if (current.value === targetValue) {
// 無論 current 是不是頭節點,操作都完全一樣
prev.next = current.next;
break; // 找到就完成任務,跳出迴圈
}
prev = current;
current = current.next;
}
// 返回 dummy.next,這永遠是串列的「新」頭部
return dummy.next;
}
這做法的流程圖簡單多了,只有唯一的出口返回:
dummy
節點是我們在串列前刻意增加的哨兵節點(Sentinel Node),從根本上改變了這個問題的資料結構。
可以讓「刪除頭節點」和「刪除中間節點」沒有任何區別,因為每一個節點(包括頭節點)都有一個前驅節點。
所有刪除操作都統一為 prev.next = prev.next.next;
。
特殊情況消失了,問題被徹底簡化。
1. 使用 Dummy Node(虛擬節點)
當你需要處理頭節點的特殊情況時,加入一個虛擬節點可以讓所有情況統一。
2. 重新思考資料結構
如果一個操作需要很多特殊判斷,問自己:能否重新設計資料結構來消除這些判斷?
3. 尋找模式
重複的程式碼模式通常意味著可以抽象化。找出這些模式,將它們提取為共用邏輯。
與其追求「聰明」或「優雅」的演算法,我們更應追求健壯、直白、沒有廢話的程式碼。
把寶貴的腦力,投入到思考「資料應該長什麼樣子」以及「如何設計資料能讓邏輯變得最簡單」。
當你的資料結構對了,程式碼自己會變好。
這才是專業工程師的好品味。