註釋
正如我們在 程式碼結構 一文所瞭解到的那樣,註釋可以是以 //
開始的單行註釋,也可以是 /* ... */
結構的多行註釋。
我們通常通過註釋來描述程式碼怎樣工作和為什麼這樣工作。
乍一看,寫註釋可能很簡單,但初學者在程式設計的時候,經常錯誤地使用註釋。
糟糕的註釋
新手傾向於使用註釋來解釋“程式碼中發生了什麼”。就像這樣:
// 這裡的程式碼會先做這件事(……)然後做那件事(……)
// ……誰知道還有什麼……
very;
complex;
code;
複製程式碼
但在好的程式碼中,這種“解釋性”註釋的數量應該是最少的。嚴格地說,就算沒有它們,程式碼也應該很容易理解。
關於這一點有一個很棒的原則:“如果程式碼不夠清晰以至於需要一個註釋,那麼或許它應該被重寫。”
配方:分解函式
有時候,用一個函式來代替一個程式碼片段是更好的,就像這樣:
function showPrimes(n) {
nextPrime:
for (let i = 2; i < n; i++) {
// 檢測 i 是否是一個質數(素數)
for (let j = 2; j < i; j++) {
if (i % j == 0) continue nextPrime;
}
alert(i);
}
}
複製程式碼
更好的變體,使用一個分解出來的函式 isPrime
:
function showPrimes(n) {
for (let i = 2; i < n; i++) {
if (!isPrime(i)) continue;
alert(i);
}
}
function isPrime(n) {
for (let i = 2; i < n; i++) {
if (n % i == 0) return false;
}
return true;
}
複製程式碼
現在我們可以很容易地理解程式碼了。函式自己就變成了一個註釋。這種程式碼被稱為 自描述型 程式碼。
配方:建立函式
如果我們有一個像下面這樣很長的程式碼塊:
// 在這裡我們新增威士忌(譯註:國外的一種酒)
for(let i = 0; i < 10; i++) {
let drop = getWhiskey();
smell(drop);
add(drop, glass);
}
// 在這裡我們新增果汁
for(let t = 0; t < 3; t++) {
let tomato = getTomato();
examine(tomato);
let juice = press(tomato);
add(juice, glass);
}
// ...
複製程式碼
我們像下面這樣,將上面的程式碼重構為函式,可能會是一個更好的變體:
addWhiskey(glass);
addJuice(glass);
function addWhiskey(container) {
for(let i = 0; i < 10; i++) {
let drop = getWhiskey();
//...
}
}
function addJuice(container) {
for(let t = 0; t < 3; t++) {
let tomato = getTomato();
//...
}
}
複製程式碼
同樣,函式本身就可以告訴我們發生了什麼。沒有什麼地方需要註釋。並且分割之後程式碼的結構也更好了。每一個函式做什麼、需要什麼和返回什麼都非常地清晰。
實際上,我們不能完全避免“解釋型”註釋。例如在一些複雜的演算法中,會有一些出於優化的目的而做的一些巧妙的“調整”。但是通常情況下,我們應該儘可能地保持程式碼的簡單和“自我描述”性。
好的註釋
所以,解釋性註釋通常來說都是不好的。那麼哪一種註釋才是好的呢?
描述架構 : 對元件進行高層次的整體概括,它們如何相互作用、各種情況下的控制流程是什麼樣的……簡而言之 —— 程式碼的鳥瞰圖。有一個專門用於構建程式碼的高層次架構圖,以對程式碼進行解釋的特殊程式語言 UML。絕對值得學習。
記錄函式的引數和用法:有一個專門用於記錄函式的語法 JSDoc:用法、引數和返回值。
例如:
/**
* 返回 x 的 n 次冪的值。
*
* @param {number} x 要改變的值。
* @param {number} n 冪數,必須是一個自然數。
* @return {number} x 的 n 次冪的值。
*/
function pow(x, n) {
...
}
複製程式碼
這種註釋可以幫助我們理解函式的目的,並且不需要研究其內部的實現程式碼,就可以直接正確地使用它。
順便說一句,很多諸如 WebStorm 這樣的編輯器,都可以很好地理解和使用這些註釋,來提供自動補全和一些自動化程式碼檢查工作。
當然,也有一些像 JSDoc 3 這樣的工具,可以通過註釋直接生成 HTML 文件。你可以在 usejsdoc.org/ 閱讀更多關於 JSDoc 的資訊。
為什麼任務以這種方式解決:寫了什麼程式碼很重要。但是為什麼 不 那樣寫可能對於理解正在發生什麼更重要。為什麼任務是通過這種方式解決的?程式碼並沒有給出答案。
如果有很多種方法都可以解決這個問題,為什麼偏偏是這一種?尤其當它不是最顯而易見的那一種的時候。
沒有這樣的註釋的話,就可能會發生下面的情況:
- 你(或者你的同事)開啟了前一段時間寫的程式碼,看到它不是最理想的實現方式。
- 你會想:“我當時是有多蠢啊,現在我真是太聰明瞭”,然後用“更顯而易見且正確的”方式重寫了一遍。
- ……重寫的這股衝動勁是好的。但是在重寫的過程中你發現“更顯而易見”的解決方案實際上是有缺陷的。你甚至依稀地想起了為什麼會這樣,因為你很久之前就已經嘗試過這樣做了。於是你又還原了那個正確的實現,但是時間已經浪費了。
解決方案的註釋非常的重要。它們可以幫助你以正確的方式繼續開發。
程式碼有哪些巧妙的特性?它們被用在了什麼地方:如果程式碼存在任何巧妙和不顯而易見的方法,那絕對需要註釋。
總結
一個好的開發者的標誌之一就是他的註釋:它們的存在甚至它們的缺席(譯註:在該註釋的地方註釋,在不需要註釋的地方則不註釋,甚至寫得好的自描述函式本身就是一種註釋)。
好的註釋可以使我們更好地維護程式碼,一段時間之後依然可以更高效地回到程式碼高效開發。
註釋這些內容:
- 整體架構,高層次的觀點。
- 函式的用法。
- 重要的解決方案,特別是在不是很明顯時。
避免註釋:
- 描述“程式碼如何工作”和“程式碼做了什麼”。
- 避免在程式碼已經足夠簡單或程式碼有很好的自描述性而不需要註釋的情況下,還寫些沒必要的註釋。
註釋也被用於一些如 JSDoc3 等文件自動生成工具:他們讀取註釋然後生成 HTML 文件(或者其他格式的文件)。
本文首發於微信公眾號「技術漫談」,歡迎微信搜尋關注,訂閱更多精彩內容。
現代 JavaScript 教程:開源的現代 JavaScript 從入門到進階的優質教程。React 官方文件推薦,與 MDN 並列的 JavaScript 學習教程。
線上免費閱讀:zh.javascript.info
掃描下方二維碼,關注微信公眾號「技術漫談」,訂閱更多精彩內容。