這篇文章的原文在這裏 (medium.com/@bpnorl...java
「乾淨的代碼應該像寫好的散文同樣」 - Robert C. Martin程序員
不良代碼的通病就是有不少註釋。這是凌亂的源代碼最明顯的跡象。正則表達式
每一個程序員的目標應該是編寫乾淨和富有表現力的代碼,以免代碼註釋。每一個變量,函數和類的目的應該隱含在其名稱和結構中。函數
當其餘人讀取您的代碼時,他們不該該閱讀註釋以瞭解你的代碼正在作什麼。命名良好的類和函數應該引導讀者經過你的代碼,就像一本寫得很好的小說同樣。當讀者看到一個新的類或功能時,他們不該該對他們在裏面看到的東西感到困惑難以理解。單元測試
請記住,開發人員的工做時間不多花在編寫代碼上,花在閱讀代碼和理解代碼上的時間要多得多。測試
在代碼中命名是很是重要的。您應該花費大量精力準確而精確地命名每一段代碼,以便其餘開發人員可以理解您的代碼。ui
// 按狀態查找員工
List<Employee> find(Status status) {
...
}
複製代碼
在此示例中,名稱find不夠描述,所以此函數的做者須要留下描述函數功能的描述性註釋。當咱們看到從另外一個模塊調用的find函數時,它的做用是一個謎。它發現了什麼?到底是什麼意思?它返回了它發現的東西嗎?怎麼找到它發現的東西?就像鮑勃叔叔在他的書《Clean Code》中所說,若是你須要寫註釋,你就沒法經過代碼表達本身真實的用意。spa
咱們不但願檢查每一個函數上面的註釋,以瞭解它的做用。翻譯
List<Employee> getEmployeesByStatus(Status status) {
...
}
複製代碼
如今很明顯能看出來這個函數的具體做用,這使得註釋變得多餘。這讓我想到了註釋糟糕的下一個方式。版本控制
這些混亂了你的代碼,徹底不必。
//此函數發送電子郵件
void sendEmail() {
...
}
//此函數發送電子郵件
public class Employee {
...
}
/ **
* @param title CD的標題
* @param做者CD的做者
* @param track CD上的曲目數
* /
public void addCd(String title, String author, int tracks) {
...
}
複製代碼
多數狀況是強制冗餘。不少公司在每一個功能和類別上都要求這一點。若是你的上司要求這樣作,請他們不要。
若是您有一個很長的功能或須要記錄代碼的哪一部分作了什麼,那麼您可能違反了這些規則:
這是一個例子
//此函數計算價格,與銷售額進行比較
//促銷,檢查價格是否有效,而後
//向用戶發送促銷電子郵件
public void doSomeThings(){
//計算價格
...
...
...
//將計算出的價格與促銷活動進
...
...
...
//檢查計算的價格是否有效
...
...
...
//向用戶發送促銷信息
...
...
...
}
複製代碼
當你成功地將邏輯的每一個部分封裝到一個單獨的函數中時,代碼不須要註釋就會表現的應該像它的做用描述同樣。
重構以下:
public void sendPromotionEmailToUsers(){
calculatePrices();
compareCalculatedPricesWithSalesPromotions();
checkIfCalculatedPricesAreValid();
sendPromotionEmail();
}
複製代碼
而不是註釋代碼的每一個部分,每一個邏輯塊應該很好地封裝在它本身的函數中。
首先,這提升了可讀性。每一個代碼塊沒必要逐行讀取。咱們能夠簡單地讀取輔助函數名稱並理解它的做用。若是咱們想要了解每一個函數內部的更多細節,就能去看具體實現。
其次,它提升了可測試性。在上面的示例中,咱們能夠爲每一個函數單獨進行單元測試。若是不封裝這些單獨的函數,則很難測試較大函數sendPromotionEmailToUsers()的每一個部分。執行多個功能的功能很難測試。
最後,它提升了可重構性。經過將邏輯的每一個部分封裝到本身的函數中,未來更改維護更容易,而且單獨功能的函數會被隔離以僅更改該函數的行爲。當咱們使用局部變量的長函數在整個函數中持續存在時,因爲函數的緊耦合,很難在不致使其餘地方變化的狀況下重構函數。
註釋掉的代碼應該被視爲roadkill。不要看它,不要聞它,不要問它從哪裏來,只是擺脫它。保持它的時間越長,其他代碼聞到的時間就越長......
/ *
public void oldFunction(){
noOneRemembersWhyIAmHere();
tryToUnCommentMe();
iWillProbablyCauseABuildFailure();
HAHAHA();
}
* /
複製代碼
儘管刪你不刪別人更不敢刪。若是你之後須要它,你能夠隨時檢查版本控制系統,由於你確定用了VCS,對嗎?(若是不是當我沒說)
不要寫TODO註釋,而不只僅是......作到了嗎?大多數時候這些註釋都會被遺忘,後來可能變得無關或錯誤。當另外一個程序員稍後看到TODO註釋時,他們如何知道是否還須要這樣作?
不過偶爾TODO註釋是好的,若是你正在等待另外一個隊友的合併(通常不會過久)。就能夠這麼作,直到你能夠進行修復並提交它。
「當你以爲有必要寫註釋時,首先要嘗試重構代碼,以便任何註釋都變得多餘。」 - Martin Fowler
當Jimmy在他寫的新功能上面打上註釋時,他認爲他正在幫助任何看到他的代碼的將來開發人員。其實呢他真正在作的是設置一個陷阱。他的註釋多是彌天大謊(沒有雙關語意圖)蟄伏數月或數年沒有被觸及,只是等待成爲一個使人討厭的陷阱。而後有一天,在數百個重構和需求變動之一中,他的註釋從一些遙遠的模塊中失效,可是仍然在錯誤的引導着無數的接盤俠。
當你更改一行代碼時,你怎麼知道你更改的代碼會不會使其餘地方的註釋無效?沒有辦法知道
public class User {
...
//它包含用戶的名字和姓氏
String name;
...
}
複製代碼
而後,需求更改,他們但願將名稱拆分爲firstName和lastName。
public class User {
...
// 它包含用戶的名字和姓氏
String firstName;
String lastName;
...
}
複製代碼
註釋如今已經錯了。你能夠更新註釋以反映更改,可是你是否真的想在每次更改後手動維護全部註釋?你是開發人員,而不是文檔。
可是這個註釋很容易被注意到而且沒有問題須要改變。可是你很難保證在程序的其餘地方,會不會也註釋了這個參數name是用戶的名字和姓氏。更改一小塊地方的代碼,可能會讓不少的代碼註釋都失效。
讓咱們看另外一個例子:
//根據狀態處理員工
void processEmployees(){
...
List < Employee > employees = findEmployees(statusList);
...
}
//這會按狀態列表查找Employees
List < Employee > findEmployees(List < String > statusList){
...
}
複製代碼
而後有人被要求更改函數findEmployees,以便經過名稱列表而不是狀態列表查找員工。
//根據狀態處理員工
void processEmployees(){
...
List < Employee > employees = findEmployees(statusList);
...
}
//這會按狀態列表查找Employees
List < Employee > findEmployees(List < String > nameList){
...
}
複製代碼
首先,上面的註釋findEmployees已經失效,所以須要更改。沒問題,對吧?錯了。
processEmployees上面的註釋也已失效,所以也須要更改。還有多少其餘評論被這個小型重構改爲無效?這一次更改在源代碼中建立了多少註釋謊話?
替代方案:
void processEmployees(){
...
List < Employee > employees = findEmployeesByName(nameList);
...
}
List < Employee > findEmployeesByName(List < Name > nameList){
...
}
複製代碼
若是你準確而準確地命名你的函數,則不須要註釋,而且你不會在代碼中散佈謊話。
「代碼永遠不會說謊,註釋會。」 - 羅恩傑弗里斯
我知道不少開發人員都是代碼註釋的死硬支持者,對他們來講,我必須認可有時註釋是能夠的。不過你每寫一段都應當有充足的理由
若是您有複雜的SQL或正則表達式語句,請繼續編寫註釋。在代碼中乾淨利落地表達諸如此類的陳述可能很困難。在這些表達式上面添加註釋能夠幫助其餘開發人員更好地理解您的代碼。
// 格式匹配kk:mm:ss EEE,MMM dd,yyy
Pattern timePattern = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w*, \\d*, \\d*");
複製代碼
若是你須要警告其餘開發人員這段代碼可能發生的bug,能夠在此代碼附近留下注釋。這些註釋能夠充當代碼中神祕行爲的先兆,併爲你的代碼增長價值。
若是你實在命名廢,那就要爲你沒有能力寫出富有表現力的代碼而負責,並寫下注釋代表本身的意圖。
若是你必須撰寫註釋,請確保它是本地的。遠離其引用的非本地評論註定會失效並變成謊話。引用函數或變量的註釋應直接位於其上方。警告註釋能夠在它引用的代碼的上方或旁邊。若是您的IDE支持註釋突出顯示,請使您的警告註釋從其他代碼中脫穎而出。
我已經創建了對代碼註釋的感覺。我鄙視他們,但我知道有時他們是須要的。
因此,請中止寫這麼多註釋。
本文是做者在推特上看到國外一位大神 布萊恩·諾蘭德 的論述,深覺得然所以翻譯後加以修飾進行分享的。但願從此本身的代碼也能像散文同樣優雅。
由於Java官方推特號轉推了,國外就這個問題也產生了很是激烈的討論,有興趣的能夠看下:
Don’t stop writing code comments
相關片斷:
由於是對做者原文的翻譯,尊重做者內容我就不作修改了。
本人的觀點是:代碼自己是必定須要乾淨、含義清晰、表達準確的,可是這並不表明全部註釋就徹底無用,這件事不是絕對性的。
其一:咱們不能假設全部人英語都要達標(甚至還有用拼音命名的--這種人是必須強制要求寫註釋的)
其二:原做者的意思應該是反對開發過程當中無效無用的註釋,但並無說起版本發行須要提供API接口給被人用的doc註釋。文檔註釋是必定須要的,便於使用者對接口的理解。由於文檔跟版本號綁定,接下來的開發中你再也不須要維護這些註釋。可是版本發行前頗有可能會遭遇不少需求變動的,除非每次需求變動都對doc註釋維護一次。不然最後直接生成的doc頗有可能就會有錯誤。最好的辦法是版本發行前統一編寫doc註釋生成文檔。
其三:針對國內的狀況,更好的建議應該是:代碼儘量優雅乾淨、命名規範。爲了照顧其餘開發人員必要時必定寫註釋,註釋附上時間以及版本號,做爲接盤俠更改祖傳代碼時儘量交代清楚需求變動理由以及附上時間,及時更新註釋。但儘量減小冗餘註釋(即代碼自己可以表達清楚的,無需註釋以避免後期須要維護更新)以避免對接盤俠形成誤導。原文做者有提到每寫一段註釋都要給本身一個這段註釋存在的理由,大概就是這個意思吧。
其四:不少人沒法理解的緣由,是由於沒有仔細看完,若是你夠細心,你就會發現原文diss的都是
// xxxx
複製代碼
而不是
/** * xxxx * @author * @date */
複製代碼
OVER