正如我們在 代碼結構 壹章所了解到的那樣,注釋可以是以 //
開始的單行注釋,也可以是 /* ... */
結構的多行注釋。
我們通常通過注釋來描述代碼怎樣工作和爲什麽這樣工作。
乍壹看,寫注釋可能很簡單,但初學者在編程的時候,經常錯誤地使用注釋。
新手傾向于使用注釋來解釋“代碼中發生了什麽”。就像這樣:
// 這裏的代碼會先做這件事(……)然後做那件事(……) // ……誰知道還有什麽…… 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 文檔。妳可以在 https://jsdoc.app 閱讀更多關于 JSDoc 的信息。
爲什麽任務以這種方式解決?
寫了什麽代碼很重要。但是爲什麽 不 那樣寫可能對于理解正在發生什麽更重要。爲什麽任務是通過這種方式解決的?代碼並沒有給出答案。
如果有很多種方法都可以解決這個問題,爲什麽偏偏是這壹種?尤其當它不是最顯而易見的那壹種的時候。
沒有這樣的注釋的話,就可能會發生下面的情況:
妳(或者妳的同事)打開了前壹段時間寫的代碼,看到它不是最理想的實現方式。
妳會想:“我當時是有多蠢啊,現在我真是太聰明了”,然後用“更顯而易見且正確的”方式重寫了壹遍。
……重寫的這股沖動勁是好的。但是在重寫的過程中妳發現“更顯而易見”的解決方案實際上是有缺陷的。妳甚至依稀地想起了爲什麽會這樣,因爲妳很久之前就已經嘗試過這樣做了。于是妳又還原了那個正確的實現,但是時間已經浪費了。
解決方案的注釋非常的重要。它們可以幫助妳以正確的方式繼續開發。
代碼有哪些巧妙的特性?它們被用在了什麽地方?
如果代碼存在任何巧妙和不顯而易見的方法,那絕對需要注釋。
壹個好的開發者的標志之壹就是他的注釋:它們的存在甚至它們的缺席(譯注:在該注釋的地方注釋,在不需要注釋的地方則不注釋,甚至寫得好的自描述函數本身就是壹種注釋)。
好的注釋可以使我們更好地維護代碼,壹段時間之後依然可以更高效地回到代碼高效開發。
注釋這些內容:
整體架構,高層次的觀點。
函數的用法。
重要的解決方案,特別是在不是很明顯時。
避免注釋:
描述“代碼如何工作”和“代碼做了什麽”。
避免在代碼已經足夠簡單或代碼有很好的自描述性而不需要注釋的情況下,還寫些沒必要的注釋。
注釋也被用于壹些如 JSDoc3 等文檔自動生成工具:它們讀取注釋然後生成 HTML 文檔(或者其他格式的文檔)。