前言
最令程序員頭痛的事情莫過于閱讀別人的代碼,但其實時間一久閱讀自己的代碼也會很痛苦。 問題不是出在『自己或別人』,而是在代碼本身。
必要的注釋可以闡明實現(xiàn)細節(jié)和設計意圖,以此節(jié)約自己和別人的時間。 然而很多時候注釋起的作用卻適得其反,比如自動生成的過多的注釋分散閱讀者的注意力, 而過期的失效的注釋更是誤導閱讀者。
自動生成的注釋
代碼注釋的泛濫想必是從Eclipse,Visual Studio等IDE開始的。 這些IDE提供了很多快捷功能,生成類/接口的骨架,具有Getter/Setter的屬性等等。 如果用過IDE,下面的代碼你一定不會陌生:
/** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub }
上述6行代碼中的4行注釋包含的信息量是0,既沒有闡釋參數(shù)args是何物,也沒有說明main的用途。 然而大量的項目中都充斥著這樣的自動生成注釋。
『建議』:如果有參數(shù)或機制需要說明,請補充這些信息。否則請刪除自動生成注釋。 當然,用于生成文檔的注釋除外。
過多的注釋
總會有人不厭其煩地編寫長篇累牘的注釋,或無微不至,或語焉不詳,或晦澀難懂,或文采飛揚。 總之沒有幫助我更快閱讀代碼的注釋都是失敗的注釋。
為了說明問題,Harttle克隆了4.x Linux Kernel源碼, 來大致分析一下其注釋行數(shù)。 我們知道內核代碼95%以上是C語言,所以統(tǒng)計.c文件就足夠說明問題了。
➜ linux git:(master) git clone git@github.com:torvalds/linux.git --depth=1 ➜ linux git:(master) find . -name "*.c" -o -name "*.h" -exec grep -E '^\s*((\*)|(/[/*]))' {} \; | wc -l 724804 ➜ linux git:(master) find . -name "*.c" -o -name "*.h" -exec cat {} \; | wc -l 4018961 ➜ linux git:(master) node > 724804/(4018961-724804) 0.22002715717556875
內核倉庫中的代碼大概是402萬行(未移除空行),其中注釋72萬行,占比22%。 Linux內核使用低級的C語言編寫,涉及到復雜的CPU調度、內存管理,驅動程序。 因此注釋會偏多一些,一般的項目注釋應小于這個數(shù)值。
『建議』:如果你的代碼中注釋超過了20%,那么顯然你過度注釋了。
文件頭注釋
很多編輯器/IDE都會生成默認的文件頭,例如:
/** * @file /tmp/xxx.js * @author harttle(yangjvn@126.com) * @date 2016-08-30 22:33 * @description A XXX Implementation for XXX. */
文件頭注釋清晰地列出了文件的作者、功能描述等信息,看起來很有用。 不過這樣的文件頭存在的問題在于其維護性:
文件頭注釋意在維護代碼文件的元信息,以便在分發(fā)和部署過程中維護作者版權等信息。 然而在擁有版本控制的代碼倉庫中,這些信息不再需要手動維護,甚至可以通過git blame查看每一行代碼的作者和時間信息。
『建議』:使用版本控制工具,刪除文件頭注釋。版權信息可在構建或分發(fā)時生成。
冗余的注釋
意圖非常清楚的代碼原則上不需要注釋,多余的注釋反而會造成維護性問題。 尤其是非英語母語的作者常常會掉到這個坑里。比如變量和函數(shù)的注釋:
/* * 獲取用戶數(shù)目 */ function getUserCount(){ // 用戶的列表 var userList = []; }
這不是廢話么!冗余的注釋問題仍然在于維護性,例如調整函數(shù)功能、調整參數(shù)順序, 或者更換變量名時我們不得不更新這些注釋。否則這些注釋就會誤導下一個讀者。
【建議】:不說廢話。
抽取注釋到標識符
可能讀者也會有這樣的經驗:當我們寫了一大段代碼時,往往需要把它們分為幾塊。 然后在每一塊開頭添加一段注釋。例如:
function calcTotalCharge(movies, user){ // Calculate Movie Charge var movieCharge = 0; for(var i=0; i<movies.length; i++){ var charge = 0; if(movie.type === 'discount'){ charge = movie.charge * 0.8; } else if(movie.type === 'short'){ charge = movie.charge * 2; } else if(movie.type === 'normal'){ charge = movie.charge; } movieCharge += charge; } // Calculate User Charge var rentCharge = 0; if(user.isVIP1){ rentCharge = 10; } if(user.isVIP2){ rentCharge = 200; } else if(user.isVIP3){ rentCharge = 300; } else if(user.isVIP4){ rentCharge = 500; } // Calculate Total Charge return movieCharge + rentCharge; }
上述代碼中的三段注釋確實加速了閱讀代碼的速度, 但每當代碼需要注釋才能讀懂時就應該警醒:是不是結構設計有問題。 對于上述代碼,我們可以通過更加可復用的結構來消除注釋:
function calcTotalCharge(movies, user){ return calcMovieCharge(movies) + calcUserCharge(user); } function calcMovieCharge(movies){ var total = 0; for(var i=0; i<movies.length; i++){ total += calcSingleMovieCharge(movie); } return total; } function calcSingleMovieCharge(movie){ if(movie.type === 'discount') return movie.charge * 0.8; else if(movie.type === 'short') return movie.charge * 2; else if(movie.type === 'normal') return movie.charge; return 0; } function calcUserCharge(user){ if(user.isVIP1) return 10; else if(user.isVIP2) return 200; else if(user.isVIP3) return 300; else if(user.isVIP4) return 500; return 0; }
代碼重構之后原來的注釋就變得毫無意義,代碼意圖都被清晰的表述在標識符的命名中。 通常重構會帶來代碼量的減小,因為封裝了分支、每個單元的邏輯也更加明確。
【建議】:當我們發(fā)現(xiàn)不得不進行注釋時,需要警醒是否結構設計發(fā)生了問題。
有用的注釋
至此Harttle已描述了這么多反模式,并非為了說明代碼注釋不重要。 而是為了說明『代碼注釋存在的意義在于幫助理解代碼本身』。 例如在編寫一些Trick,Polyfill,臨時代碼,以及復雜算法時,注釋變得相當重要。 例如:
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com