請停止程式碼註釋

連理枝_發表於2019-06-04

“乾淨的程式碼應該像寫好的散文一樣” - Robert C. Martin

不良程式碼的通病就是有很多註釋。這是凌亂的原始碼最明顯的跡象。

每個程式設計師的目標應該是編寫乾淨和富有表現力的程式碼,以避免程式碼註釋。每個變數,函式和類的目的應該隱含在其名稱和結構中。

當其他人讀取您的程式碼時,他們不應該閱讀註釋以瞭解你的程式碼正在做什麼。命名良好的類和函式應該引導讀者通過你的程式碼,就像一本寫得很好的小說一樣。當讀者看到一個新的類或功能時,他們不應該對他們在裡面看到的東西感到困惑難以理解。

請記住,開發人員的工作時間很少花在編寫程式碼上,花在閱讀程式碼和理解程式碼上的時間要多得多

註釋掩蓋自身的失敗

我經常看到變數或函式名稱之上的註釋描述程式碼執行(或應該執行的操作)的內容。這些註釋給我的感受就是,這段程式碼的前任眉有辦法想象一個富有表現力的名稱,或者他們的功能不僅僅是一件事。

在程式碼中命名是非常重要的。您應該花費大量精力準確而精確地命名每一段程式碼,以便其他開發人員能夠理解您的程式碼。

// 按狀態查詢員工
List<Employee> find(Status status) {
  ...
}
複製程式碼

在此示例中,名稱find不夠描述,因此此函式的作者需要留下描述函式功能的描述性註釋。當我們看到從另一個模組呼叫的find函式時,它的作用是一個謎。它發現了什麼?究竟是什麼意思?它返回了它發現的東西嗎?怎麼找到它發現的東西?就像鮑勃叔叔在他的書《Clean Code》中所說,如果你需要寫註釋,你就無法通過程式碼表達自己真實的用意。

我們不希望檢查每個函式上面的註釋,以瞭解它的作用。

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) {
  ...
}
複製程式碼

多數情況是強制冗餘。很多公司在每個功能和類別上都要求這一點。如果你的上司要求這樣做,emm你可以嘗試請他們不要。

錯誤的抽象程度

如果您有一個很長的功能或需要記錄程式碼的哪一部分做了什麼,那麼您可能違反了這些規則:

  1. 功能應該做一件事。
  2. 功能應該很小。

這是一個例子

//此函式計算價格,與銷售額進行比較
//促銷,檢查價格是否有效,然後
//向使用者傳送促銷電子郵件
public  void doSomeThings(){

  //計算價格
  ...
    ...
    ...
  
  //將計算出的價格與促銷活動進
  ...
    ...
    ...
  
  //檢查計算的價格是否有效
  ...
    ...
    ...
  
  //向使用者傳送促銷資訊
  ...
    ...
    ...
}
複製程式碼

當你成功地將邏輯的每個部分封裝到一個單獨的函式中時,程式碼不需要註釋就會表現的應該像它的作用描述一樣。

重構如下:

public  void sendPromotionEmailToUsers(){
  calculatePrices();
  compareCalculatedPricesWithSalesPromotions();
  checkIfCalculatedPricesAreValid();
  sendPromotionEmail();
}
複製程式碼

而不是註釋程式碼的每個部分,每個邏輯塊應該很好地封裝在它自己的函式中。

首先,這提高了可讀性。每個程式碼塊不必逐行讀取。我們可以簡單地讀取輔助函式名稱並理解它的作用。如果我們想要了解每個函式內部的更多細節,就能去看具體實現。

其次,它提高了可測試性。在上面的示例中,我們可以為每個函式單獨進行單元測試。如果不封裝這些單獨的函式,則很難測試較大函式sendPromotionEmailToUsers()的每個部分。執行多個功能的功能很難測試。

最後,它提高了可重構性。通過將邏輯的每個部分封裝到自己的函式中,將來更改維護更容易,並且單獨功能的函式會被隔離以僅更改該函式的行為。當我們使用區域性變數的長函式在整個函式中持續存在時,由於函式的緊耦合,很難在不導致其他地方變化的情況下重構函式。

註釋掉的程式碼

註釋掉的程式碼應該被視為roadkill。不要看它,不要聞它,不要問它從哪裡來,只是擺脫它。保持它的時間越長,其餘程式碼聞到的時間就越長......

/ *
public void oldFunction(){
  noOneRemembersWhyIAmHere();
  tryToUnCommentMe();
  iWillProbablyCauseABuildFailure();
  HAHAHA();
}
* /
複製程式碼

儘管刪你不刪別人更不敢刪。如果你以後需要它,你可以隨時檢查版本控制系統,因為你肯定用了VCS,對嗎?(如果不是當我沒說)

TODO註釋

不要寫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支援註釋突出顯示,請使您的警告註釋從其餘程式碼中脫穎而出。

最後

我已經建立了對程式碼註釋的感受。我鄙視他們,但我知道有時他們是需要的。

所以,請停止寫這麼多註釋。

本文是作者在推特上看到國外一位大神 布萊恩·諾蘭德 的論述,深以為然因此翻譯後加以修飾進行分享的。希望今後自己的程式碼也能像散文一樣優雅。

你的程式碼怎麼不寫註釋
”不好意思,我的程式碼就是不用寫註釋“ (逃

相關文章