前一章已經透過函式拆分、專用資料結構與明確命名,讓程式本身表達流程與功能意圖。結構清楚之後,仍有部分資訊無法直接寫進函式名稱或條件判斷,例如選擇某種做法的原因、功能規則的確認來源,以及看似多餘卻暫時不能移除的相容處理。
註解適合保存這些背景。它不需要逐行翻譯程式,也不應該替過長或命名含糊的實作補充說明。判斷是否需要註解時,可以先問兩個問題:這項資訊能否透過程式結構清楚表達,以及缺少它是否會讓後續修改產生錯誤判斷。
本章會說明哪些內容適合寫入註解、哪些問題應該先修改程式,以及如何讓註解隨實作一起維護。
自我說明程式碼(Self-Documenting Code)透過清楚命名、適當函式大小與明確資料結構表達意圖。讀者可以從程式看出輸入如何處理、條件如何判斷及結果如何產生,不需要依賴另一段文字重述相同動作。
例如,下列註解只把條件判斷再說一次:
// 檢查匯入數量是否有效
if (amount >= 0) {
acceptImportRow(row)
}
如果「有效」確實是一項功能概念,可以改用名稱表達:
if (isValidImportAmount(amount)) {
acceptImportRow(row)
}
新的名稱讓呼叫端直接說明判斷目的,規則細節則集中在 isValidImportAmount。日後條件改變時,只需要修改函式與測試,不會留下與實作不一致的逐行說明。
自我說明程式碼仍有表達限制。它能顯示目前採用哪項條件,通常無法說明條件的確認來源,也不適合把一段決策背景塞進函式名稱。這些資訊才是註解應該補充的內容。
有價值的註解會回答「為甚麼採用這個做法」。它可以記錄系統限制、被排除方案、必要的處理順序,或某項看似可以簡化的程式為何需要保留。
例如,Math.trunc 已經清楚顯示程式會捨去小數。註解應該補充採用這項轉換的原因:
function toExternalAmount(value) {
// 目標格式只接受整數,因此在輸出前捨去小數。
return Math.trunc(value)
}
註解中的「目標格式只接受整數」無法從 Math.trunc 直接得知。如果這項限制改變,開發人員也能知道轉換方式需要一起檢查。
下列資訊通常適合用註解補充:
| 資訊類型 | 註解需要記錄的內容 |
|---|---|
| 設計原因 | 為何選擇目前做法,以及哪些限制影響決定。 |
| 判斷順序 | 順序改變會造成甚麼功能差異。 |
| 特殊邊界 | 看似可以合併的條件為何需要分開處理。 |
| 暫時限制 | 限制目前為何存在,以及解除後要調整哪些程式。 |
| 外部格式 | 程式必須符合的欄位、精度、順序或相容條件。 |
註解應該保留完成判斷所需的最少資訊。背景需要長篇說明時,可以在註解中留下可追溯的文件或問題識別,再把完整內容放在適合維護的位置。
程式可以顯示目前如何處理輸入,卻不一定能說明這項行為來自已確認的需求、格式規範、歷史相容條件,或只是早期實作留下的結果。當來源會影響規則能否修改時,註解應該留下可確認的參考。
例如,空白代碼究竟要拒絕還是套用預設值,無法只靠條件判斷得知決策背景:
// RULE-IMPORT-07:空白代碼要保留為拒絕結果,不套用預設值。
const rejection = code === '' ? 'code_required' : null
RULE-IMPORT-07 可以對應需求紀錄、格式規格或其他可持續取得的決定。註解不必複製整份文件,只要讓後續修改者能確認規則內容、適用範圍與目前版本。
參考來源失效時,註解也會失去用途。使用檔案路徑、段落識別或問題編號時,要選擇能隨專案保存或持續查詢的形式。只有人名、日期或「以前討論過」等資訊,通常不足以還原決定內容。
目標系統在替換期間可能需要支援既有資料格式、互動方式或相依項目版本。相容程式看起來可能重複、繞路或違反目前慣例,如果沒有說明,後續修改者可能誤以為它是多餘程式而直接刪除。
相容註解至少要說明支援對象、保留原因、移除條件與追蹤位置:
function parseSourceDate(value) {
// 暫時支援來源格式 v1;所有來源改用 v2 後移除。追蹤:ISSUE-184。
return normalizeLegacyDate(value)
}
「暫時保留」沒有提供可執行的後續判斷。上例明確指出格式 v1 是相容對象,完成 v2 切換是移除條件,ISSUE-184 則連回追蹤紀錄。條件達成後,應該刪除相容程式與註解,不能讓已失效的說明繼續留在目標系統。
如果相容處理會在特定日期失效,仍要記錄實際完成條件。只有日期可能在工作尚未完成時經過,也無法說明移除前需要確認哪些輸入、資料或相依項目。
某些演算法、效能處理、安全性限制或邊界條件具有刻意安排。程式能顯示執行順序,卻不一定能說明順序背後的取捨。這類註解應該指出改動可能造成的具體影響。
例如,排序可能用來產生可穩定比較的輸出,與顯示需求無關:
// 固定輸出順序,讓相同輸入可以直接進行完整結果比較。
items.sort((left, right) => left.id - right.id)
如果只寫「依識別資料排序」,註解仍然只是重述程式。補上穩定比較的目的後,後續修改者就能判斷是否可以更換排序欄位或移除排序。
註解不能取代測試。必要順序、特殊邊界與安全性限制仍應該建立相應案例,註解負責說明原因與影響,測試負責確認程式持續符合條件。
文件註解(Documentation Comment)用來說明公開函式、類別或其他對外使用介面的契約。內容可以包含用途、輸入限制、回傳結果、可能拋出的錯誤及副作用,讓呼叫端不需要閱讀內部實作就能正確使用。
JavaScript 可以使用 JSDoc 記錄這些資訊:
/**
* 將數字文字解析成非負安全整數。
* @param {string} value - 只包含十進位數字的文字。
* @returns {number} 解析後的非負安全整數。
* @throws {TypeError} 輸入格式錯誤或超出安全整數範圍時拋出。
*/
export function parseCount(value) {
const count = Number(value)
if (typeof value !== 'string' || !/^\d+$/.test(value) || !Number.isSafeInteger(count)) {
throw new TypeError('value 必須是非負安全整數文字')
}
return count
}
這段文件註解描述 parseCount 的輸入格式、成功結果與失敗行為,實作也確實符合三項內容。如果函式可能修改呼叫端傳入的資料、保存狀態或產生其他外部影響,也應該在契約中說明。
私有函式不需要一律加入完整文件註解。名稱、參數與回傳值已經足以表達用途時,額外標註只會增加維護內容。公開程度、使用範圍及錯誤使用的影響,才是決定文件註解詳細程度的依據。
逐行敘述程式動作會增加閱讀量,卻沒有提供新資訊。當實作變更而註解沒有同步修改時,這類說明還可能讓讀者相信過時行為。
以下註解可以直接刪除:
// 將重試次數加一
retryCount += 1
下列註解也沒有補充判斷目的:
// 如果狀態等於 completed
if (status === 'completed') {
publishResult(result)
}
如果條件的功能意義不清楚,應該優先調整名稱或抽取函式。例如,isReadyToPublish(status) 比逐字描述比較動作更容易表達目的。如果條件背後還有無法從名稱得知的限制,再另外補充原因。
一段註解如果需要逐步說明變數如何改變、各分支如何執行及最後如何組合結果,通常表示程式結構仍不夠清楚。前一章介紹的重新命名、縮小作用域、抽取函式與拆分責任,應該先用來降低理解成本。
例如,複雜的重試條件可以先抽成能表達目的的函式:
if (shouldRetry(error, attemptCount)) {
await retryOperation()
}
shouldRetry 應該由測試說明各種錯誤與次數的判斷結果。如果最大次數或特定錯誤限制來自外部規範,則可以在規則實作旁留下來源,不需要在呼叫端用長篇註解解釋所有分支。
抽取後如果仍需要大量文字才能說明輸入與結果,可能表示函式同時負責多項工作,或使用的資料結構缺少明確狀態。此時應該繼續檢查程式邊界,避免讓註解成為難懂實作的固定補丁。
把停用程式碼改成註解會讓原始碼同時存在目前實作與歷史片段。讀者無法判斷這段內容是否仍有用途、是否可以恢復,以及它與目前功能規則有何關係。
已刪除內容應該由 Git 歷史保存。需要查找時,可以從提交紀錄確認刪除原因、當時版本與相關修改。這種方式比在原始碼中保留無法執行的片段更容易追溯,也不會干擾目前程式。
如果某段程式預計之後恢復,應該記錄恢復條件與追蹤項目,並將實作保存在適合的版本紀錄中。無法說明恢復條件的停用片段,不應該因為「可能還會用到」而長期留在註解內。
TODO 與 FIXME 可以提醒尚未完成的工作或已知問題,但單獨留下標記無法說明原因、影響與完成方式。專案應該先定義兩者用途,並且要求每項註記能連回持續維護的追蹤紀錄。
| 標記 | 建議用途 | 至少需要記錄的內容 |
|---|---|---|
TODO |
已確認需要完成,但目前尚未納入本次修改的工作。 | 保留原因、完成或移除條件、追蹤項目。 |
FIXME |
已確認目前實作會產生錯誤或不完整結果。 | 問題情境、預期修正結果、追蹤項目。 |
註記可以維持一行,但內容要能支持後續處理:
// TODO(ISSUE-184):來源格式 v1 停用後,移除相容轉換。
// FIXME(ISSUE-207):空白代碼會被接受;修正後必須通過案例 IMP-012。
只有「之後處理」、姓名或建立日期的註記,無法判斷工作是否仍需要進行。追蹤項目完成、需求改變或相關程式刪除時,也要同步移除註記。
TODO 與 FIXME 不應該取代目前修改範圍內必要的錯誤處理。如果問題會讓本次功能無法符合驗收條件,就要先修正或明確阻止功能進入下一階段,不能只留下註記後繼續執行。
註解會影響讀者如何理解與修改程式,因此需要和實作一起檢查。功能規則、外部格式、相依項目或錯誤行為變更時,要同步更新相關註解與文件註解。
程式修改完成後,可以使用下列問題檢查:
TODO 與 FIXME 是否具有原因、完成條件與追蹤方式?程式審查也要檢查註解是否正確,不能只確認語法與執行結果。錯誤的註解可能比沒有註解更難發現,因為它會提供看似合理但已經過時的解釋。
TODO 與 FIXME 要記錄原因、完成條件及追蹤方式,不能取代目前範圍內必要的修正與錯誤處理。