好的程式碼不需要註解,因為程式碼本身就能被讀懂。
程式碼本身,才是唯一的、絕對的事實來源。
註解不是。
註解充其量只是一個旁觀者的論述,它會過期、會腐爛、會跟不上程式碼的變化,最後,它會說謊騙你。
一個撒謊的註解,比沒有註解要壞上一萬倍。
它會主動地誤導下一個讀程式碼的人,把他們引導到錯誤的理解上,進而製造出更隱晦
的臭味 bug。
把時間花在改進命名與結構,而不是寫註解。
浪費時間寫註解,卻不願意花半秒鐘去想一個好名字。
// 🔴 臭味:這段註解的存在,就是為了掩蓋命名的懶惰。
// 計算稅金
function c(p) {
return a * 0.05;
}
// 🔴 臭味:甚至忘了更新註解
// 金額乘以 5% 的稅率
function c(p) {
return amount * 0.05;
}
// 🟢 好味道:不需要任何註解。命名清晰。
const SALE_TAX_RATE = 0.07;
function calculateAmountWithTax(amount) {
return amount * (1 + SALE_TAX_RATE);
}
程式碼即文件: calculateAmountWithTax
和 SALE_TAX_RATE
這些名字,已經把你要知道的事情都告訴你了。它清楚、準確、不會說謊誤導你。
投資報酬率: 花在想一個好名字上的 10 秒鐘,會在未來幾年裡,為每一個讀這段程式碼的人省下無數的時間和精力。花在寫一段爛註解上的 5 秒鐘,則是在為未來製造技術債。
註解是個必要的惡,只能在特殊情況下使用。
一個好的註解,永遠不解釋「做了什麼 What」,只解釋「為什麼這麼做 Why」。
反直覺程式碼,要解釋「為什麼」這麼奇怪: 因為某些外部原因(比如要繞過某個瀏覽器 bug 或硬體缺陷)而寫得非常違反直覺時,你必須寫註解。否則,可能下一個工程師會「修正」你這段看起來很蠢的正確程式碼,然後毀掉一切。
// 好註解:解釋「為什麼」
// 底下的位移操作看起來很怪,但這是為了相容舊版的 XYZ 晶片。
// 詳見硬體手冊第 253 頁。
value = (value << 1) | (value >> 31);
提供背景脈絡: 註解可以連結到外部的規格文件、研究論文、或 issue tracker。它為程式碼提供了它本身無法承載的背景故事。
# 好註解:提供背景
# 這裡的演算法實作了 Knuth-Morris-Pratt 演算法。
# 關於 KMP 的詳細解釋,請參考:[論文連結]
def kmp_search(text, pattern):
# ...
如果你看到這些註解,就代表程式碼發臭了。
殭屍程式碼 (Zombie Code)
// const oldLogic = a * b;
const newLogic = a + b;
為什麼要留下那段被註解掉的程式碼?是還在用嗎?
版本控制系統就是為此而生的。
如果你真的需要找回舊程式碼,去 git log
裡找。
空頭支票 (Empty Promises):
// TODO: 之後要來重構這裡
TODO
註解通常是都不會實現。
如果這件事真的「TO DO」,那就去 issue tracker 開一張票。
如果不是,那就現在修好它,或者刪掉它。
一個好的 Commit Message (提交訊息),遠比程式碼裡的註解更有價值。
它在一個更高的維度上解釋了「為什麼」。
fix bug
fix(API): 修正 calculateTax 在 amount 為 null 時的崩潰問題
之前的實作沒有處理 amount 為 null 的邊界情況,會導致
TypeError。
此修改在函式開頭加入了一個保護式敘述 (guard clause),
若 amount 為 null 或 undefined,直接回傳 0。
這符合 spec 文件 v1.2 中定義的稅務計算規則。
相關 Ticket: #451
這份提交訊息,很好解釋了what, why, how,而且它被永久地記錄在版本歷史裡,不會像程式碼裡的註解一樣過期。
你的第一選擇永遠是重構。
你的第二選擇是重命名。
你的最後、最不情願的選擇,才是寫註解。
讓程式碼自己說話、註解只補充關鍵背景,這就是在消除你程式碼的臭味。