第 12 章 不寫註釋的四個藉口
程式碼內的文件在軟體設計中起著至關重要的作用。註釋對於幫助開發人員理解系統和高效工作至關重要,但是註釋的作用不止於此。文件在抽象中也起著重要作用。沒有註釋,您就無法隱藏複雜性。最後,編寫註釋的過程(如果正確完成)實際上會改善系統的設計。 相反,如果沒有很好的文件記錄,那麼好的軟體設計會失去很多價值。
不幸的是,這種觀點並未得到普遍認同。大部分的生產程式碼基本上不包含任何註釋。許多開發人員認為註釋是浪費時間。其他人則看到了註釋中的價值,但不知何故從不動手編寫它們。幸運的是,許多開發團隊認識到了文件的價值,並且感覺這樣的團隊越來越普及了。但是,即使在鼓勵文件的團隊中,註釋也經常被視為繁瑣的工作,而且許多開發人員也不瞭解如何編寫註釋,因此生成的文件通常是平庸的。文件不足會給軟體開發帶來巨大且不必要的拖累。
在本章中,我將討論開發人員用來避免寫註釋的藉口,以及註釋真正重要的原因。然後,第 13 章將描述如何編寫好的註釋,其後的幾章將討論相關問題,例如如何選擇變數名以及如何使用文件來改進系統的設計。我希望這些章節能使您相信三件事:好的註釋可以對軟體的整體質量產生很大的影響;寫好註釋並不難;並且(可能很難相信)寫註釋實際上很有趣。
當開發人員不寫註釋時,他們通常會以以下一種或多種藉口為自己的行為辯護:
- “好的程式碼是自解釋的。”
- “我沒有時間寫註釋。”
- “註釋很容易過時,並會產生誤導。”
- “我所看到的註釋都是毫無價值的,何必呢?”
在後續的章節中,我將依次討論這些藉口。
12.1 好的程式碼是自解釋的
有人認為,如果程式碼編寫得當,則程式碼是顯而易見的,不需要註釋。這是一個美味的神話,就像謠言說冰淇淋對您的健康有益:我們真的很想相信!不幸的是,事實並非如此。可以肯定的是,在編寫程式碼時可以做一些事情來減少對註釋的需求,例如選擇好的變數名(請參閱第 14 章)。儘管如此,仍有大量設計資訊無法用程式碼表示。例如,只能在程式碼中對類介面的一小部分進行形式化的指定,例如其方法的簽名。介面的非形式化部分,例如對每個方法的作用或其結果的含義的高層級描述,只能在註釋中進行描述。還有許多程式碼中無法描述的東西,比如特定設計決策背後的考量,或者呼叫特定方法的前提條件。
一些開發人員認為,如果其他人想知道某個方法的作用,那麼他們應該只需要閱讀該方法的程式碼:這將比任何註釋都更準確。讀者確實可能會透過閱讀其程式碼來推斷該方法的抽象介面,但這既費時又痛苦。另外,如果你在編寫程式碼時期望使用者會閱讀方法的實現,那你將嘗試使每個方法儘可能短,以便於閱讀。如果該方法做的事情不簡單,你會將其分解為幾個較小的方法。這將導致大量的淺方法。此外,這並沒有真正使程式碼更易於閱讀:為了理解頂層方法的行為,讀者可能需要了解其內嵌的方法的行為。對於大型系統,讓使用者透過閱讀程式碼來了解其行為是不切實際的。
此外,註釋是抽象的基礎。回顧第 4 章,抽象的目的是隱藏複雜性:抽象是實體的簡化檢視,它保留了必要的資訊,但省略了可以安全忽略的細節。如果使用者必須閱讀方法的程式碼才能使用它,那就沒有任何抽象可言:方法的所有複雜性都將暴露出來。沒有註釋,方法的唯一抽象就是其宣告,該宣告指定其名稱以及其引數和返回結果的名稱和型別。該宣告缺少太多基本資訊,無法單獨提供有用的抽象。例如,提取子字串的方法可能有兩個引數,起始和結束,表示要提取的字元範圍。僅憑宣告,無法確定提取的子字串是否將包含結束位置所指向的字元,或者如果起始位置在結束位置的後面時會發生什麼。註釋使我們能夠得到呼叫者所需的額外資訊,從而在隱藏實現細節的同時得到簡化的檢視。用人類語言(例如英語)寫註釋也很重要,雖然這會使它們不如程式碼精確,但也提供了更好的表達能力,因此我們可以建立簡單直觀的描述。如果要使用抽象來隱藏複雜性,則註釋必不可少。
12.2 我沒有時間寫註釋
與其他開發任務相比,將註釋的優先順序降低是很誘人的。如果要在新增新功能和為現有功能寫註釋之間做出選擇的話,選擇新功能似乎合乎邏輯。但是,軟體專案幾乎總是處於時間壓力之下,並且總會有比編寫註釋優先順序更高的事情。因此,如果您允許取消文件的優先順序,則最終將沒有文件。
反駁該藉口的是第 3.2 節討論過的投資思維。如果您想要一個乾淨的軟體結構,以允許你長期有效地工作,那麼您必須花一些額外的時間才能建立該結構。好的註釋對軟體的可維護性有很大的影響,因此花費在它們上面的精力將很快收回成本。此外,編寫註釋不需要花費很多時間。問問您自己,假設您不需要寫任何註釋,那麼您花費了多少開發時間來寫程式碼(與設計、編譯、測試等相比)。我懷疑答案是否超過 10%。現在假設您花在寫註釋上的時間與寫程式碼所花費的時間一樣多,這應該是一個安全的上限。基於這些假設,編寫好的註釋最多也只會增加您約 10% 的開發時間。擁有良好文件的好處將迅速抵消這一成本。
此外,許多最重要的註釋是與抽象有關的,例如類和方法的頂層文件。第 15 章認為,這些註釋應該是設計過程的一部分,並且寫文件的行為是用來改進整體設計的一個重要工具。這些註釋很快就會物有所值。
12.3 註釋很容易過時,並會產生誤導
註釋有時確實會過時,但這在實踐中並不是主要問題。使文件保持最新狀態並不需要付出巨大的努力。僅當對程式碼進行了較大的更改時才需要對文件進行大的更改,並且程式碼更改將比文件的更改花費更多的時間。第 16 章討論了如何組織文件,以便在修改程式碼後儘可能容易地對其進行更新(關鍵的思想是避免重複的文件,並保持文件靠近相應的程式碼)。程式碼審查提供了一種檢測和修復陳舊註釋的有效機制。
12.4 我所看到的所有註釋都是毫無價值的
在這四個藉口中,這可能是最有價值的藉口。每個軟體開發人員都看到過沒有提供有用資訊的註釋,並且大多數現有文件充其量都是這樣。幸運的是,這個問題是可以解決的。一旦你知道怎麼做,寫出有效的文件並不難。接下來的幾章將為如何編寫良好的文件並持續進行維護提供一個框架。
12.5 良好註釋的好處
既然我已經討論了(並希望揭穿了這些)反對編寫註釋的論點,讓我們考慮一下從良好註釋中將獲得的好處。註釋背後的總體思想是捕獲設計者所想的但不能在程式碼中表示的資訊。 這些資訊從低層級的詳細資訊(例如,導致了複雜程式碼的硬體奇葩行為)到高層級的概念(例如,類的基本原理)。當其他開發人員稍後進行修改時,這些註釋將使他們能夠更快、更準確地工作。沒有文件,未來的開發人員將不得不重新研究或猜測開發人員的原始想法,這將花費額外的時間,並且如果新開發者誤解了原始設計者的意圖,則存在導致程式碼缺陷的風險。即使是原作者在修改程式碼時註釋也是有價值的:如果距離你最後一次在一段程式碼中工作已經有幾個星期了,你會忘記許多最初的設計細節。
第 2 章介紹了複雜性在軟體系統中表現出來的三種方式:
- 變更放大:看似簡單的變更需要在許多地方進行程式碼修改。
- 認知負荷:為了進行更改,開發人員必須累積大量資訊。
- 未知的未知:尚不清楚需要修改哪些程式碼,或必須考慮哪些資訊才能進行這些修改。
好的文件可以幫助解決後兩個問題。透過為開發人員提供他們進行更改所需的資訊,並使開發人員可以忽略不相關的資訊,文件可以減輕認知負荷。沒有足夠的文件,開發人員可能必須閱讀大量程式碼才能重新構建出設計人員的想法。文件還可以透過闡明系統的結構來減少“未知的未知”,從而可以清楚地瞭解與任何給定的變更相關的資訊和程式碼。
第 2 章指出,導致複雜性的主要原因是依賴性和模糊性。好的文件可以闡明依賴關係,並且可以填補空白以消除模糊性。
接下來的幾章將向您展示如何編寫好的文件。還將討論如何將文件編寫整合到設計過程中,從而改善軟體設計。