第 12 章 不寫註解的四個藉口
程式碼中的文件在軟體設計中起著至關重要的作用。註解能幫助開發人員理解系統、高效工作,但其作用不止於此。文件也是抽象的重要組成部分:沒有註解,就無法隱藏複雜性。最後,只要方法得當,編寫註解的過程本身就能改善系統設計。 反之,再好的軟體設計,缺乏良好文件也會失去很多價值。
本章概覽不寫註解的四個藉口,站得住嗎?
遺憾的是,這一觀點並未得到普遍認同。相當一部分生產程式碼幾乎沒有註解。許多開發人員認為寫註解是浪費時間;另一些人雖然看到了註解的價值,卻總是遲遲沒有動筆。好在許多開發團隊認識到了文件的價值,而且這樣的團隊似乎正逐漸增多。不過,即使團隊鼓勵編寫文件,註解也常被視為繁瑣的雜務。許多開發人員又不知道該怎麼寫,結果文件品質往往平平。文件不足給軟體開發帶來了巨大且不必要的拖累。
本章將討論開發人員不寫註解的藉口,以及註解為何如此重要。第 13 章將介紹如何寫好註解,後續幾章則討論相關問題,例如如何選擇變數名、如何藉助文件改進系統設計。我希望這些章節能讓你相信三件事:好的註解能顯著改善軟體的整體品質;寫好註解並不難;而且,寫註解其實可以很有趣,雖然這可能讓人難以相信。
當開發人員不寫註解時,他們通常會以以下一種或多種藉口為自己的行為辯護:
- “好的程式碼是自解釋的。”
- “我沒有時間寫註解。”
- “註解很容易過時,並會產生誤導。”
- “我所看到的註解都是毫無價值的,何必呢?”
下面幾個小節將依次討論這些藉口。
12.1 好的程式碼是自解釋的
有人認為,只要程式碼寫得好,邏輯就清楚,不需要註解。這是一個“誘人的謬誤”,就像“冰淇淋有益健康”的說法一樣,讓人很想相信。可惜事實並非如此。編寫程式碼時,確實可以採取一些措施來減少對註解的需求,例如選擇好的變數名(請參閱第 14 章)。但仍有大量設計資訊無法用程式碼表達。例如,類介面中只有很小一部分能在程式碼中形式化地指定,如方法簽名。介面的非形式化部分,如每個方法做什麼、結果代表什麼的高層級描述,只能透過註解說明。還有許多資訊也無法用程式碼表達,例如某個設計決策背後的考量,或在什麼情況下適合呼叫某個方法。
一些開發人員認為,要了解某個方法的作用,只需閱讀其程式碼,這比任何註解都準確。讀者確實可能從程式碼中推斷出方法的抽象介面,但這既費時又費力。而且,如果你預期使用者會閱讀方法的實作,就會盡量把每個方法寫短,以便閱讀。方法所做的事情稍微複雜一些,就會被拆成幾個較小的方法,最終產生大量淺方法。這也沒有真正提高可讀性:為了理解頂層方法的行為,讀者仍可能需要了解那些較小方法的行為。對於大型系統,要求使用者靠閱讀程式碼來了解系統行為並不現實。
此外,註解是抽象的基礎。第 4 章指出,抽象的目的是隱藏複雜性:它是實體的簡化視圖,保留必要資訊,省去可以安全忽略的細節。如果使用者必須閱讀方法的程式碼才能使用它,那就沒有任何抽象可言:方法的所有複雜性都暴露了出來。沒有註解,方法唯一的抽象就是宣告,其中給出方法名,以及參數和回傳結果的名稱與型別。宣告缺少太多必要資訊,無法獨自構成有用的抽象。例如,提取子字串的方法可能有“起始”和“結束”兩個參數,用來表示字元範圍。僅憑宣告,無法知道結果是否包含結束位置的字元,也不知道起始位置在結束位置之後時會發生什麼。註解可以補充呼叫者所需的資訊,在隱藏實作細節的同時提供簡化視圖。用人類語言(例如英語)寫註解也很重要:雖然不如程式碼精確,表達能力卻更強,能給出簡單直觀的描述。因此,要用抽象隱藏複雜性,註解必不可少。
譯者圖解宣告留下了哪些互不相容的解釋?
12.2 我沒有時間寫註解
人們很容易把寫註解放在其他開發任務之後。如果要在新增新功能和為現有功能寫註解之間選擇,前者似乎更合理。但軟體專案幾乎總有時間壓力,也總有看起來比寫註解更優先的事情。如果任由文件的優先權不斷降低,最終就不會有文件。
反駁這一藉口,可以用第 3.2 節討論過的投資思維。要有整潔的軟體結構,支援長期高效開發,就必須在前期投入額外時間來構建它。好的註解能顯著提高可維護性,投入很快就會得到回報。而且,寫註解並不需要很多時間。問問自己:假設完全不寫註解,相比設計、編譯、測試等工作,真正用於鍵入程式碼的時間佔多少?我不認為這一比例會超過 10%。再假設鍵入註解所花的時間與鍵入程式碼一樣多,這應該是一個穩妥的上限。按這些假設,寫好註解最多只會增加約 10% 的開發時間,而良好文件帶來的收益會很快抵消這點成本。
此外,許多最重要的註解與抽象有關,例如類和方法的頂層文件。第 15 章將說明,這些註解應當是設計過程的一部分,而編寫文件本身就是改進整體設計的重要工具。這些註解立即就能帶來回報。
12.3 註解很容易過時,並會產生誤導
註解有時確實會過時,但在實踐中,這未必會成為大問題。保持文件更新不需要付出很大精力。只有程式碼大幅修改時,文件才需要大幅修改,而改程式碼花費的時間會更多。第 16 章討論了如何組織文件,使它在程式碼修改後儘可能容易更新,關鍵是避免重複,並將文件放在相應程式碼附近。程式碼審查也能有效發現和修正過時的註解。
12.4 我所看到的所有註解都是毫無價值的
在四個藉口中,這一個可能最有道理。每個軟體開發人員都見過不提供有用資訊的註解,而大多數現有文件充其量也只是平平。好在這個問題可以解決。掌握方法後,寫出有用的文件並不難。接下來的幾章將提供一套思路,幫助你寫好文件並持續維護。
12.5 良好註解的好處
前面討論了反對寫註解的理由,希望也已經說明了它們為何站不住腳。現在來看看好註解的益處。註解的核心作用,是記錄設計者知道、卻無法在程式碼中表達的資訊。 這些資訊既包括底層細節,例如導致程式碼複雜的硬體特殊行為,也包括高層概念,例如類的設計理由。其他開發人員日後修改程式碼時,藉助註解能更快、更準確地完成工作。沒有文件,他們就得重新研究或猜測原設計者的想法,不僅費時,一旦理解有誤,還可能引入程式碼缺陷。即使原作者自己修改程式碼,註解也很有價值:超過幾個星期沒有接觸某段程式碼,你就會忘記最初設計時的許多細節。
第 2 章介紹了複雜性在軟體系統中表現出來的三種方式:
- 變更放大:看似簡單的變更需要在許多地方進行程式碼修改。
- 認知負荷:為了進行修改,開發人員必須掌握大量資訊。
- 未知的未知:尚不清楚需要修改哪些程式碼,或必須考慮哪些資訊才能進行這些修改。
好的文件有助於解決後兩個問題。它提供修改程式碼所需的資訊,讓開發人員可以忽略無關細節,從而減輕認知負荷。文件不足時,開發人員可能要閱讀大量程式碼,才能還原設計者的思路。文件還可以說明系統結構,讓開發人員清楚某項變更涉及哪些資訊和程式碼,從而減少“未知的未知”。
第 2 章指出,複雜性的主要來源是依賴性和模糊性。好的文件可以說明依賴關係,補足缺失的資訊,從而消除模糊性。
接下來的幾章將介紹如何寫好文件,以及如何將文件編寫融入設計過程,以改進軟體設計。
12.6 不同的觀點:註解是一種失敗
Robert Martin 在《程式碼整潔之道》中對註解持更消極的看法:
……註解充其量也只是一種必要之惡。如果程式語言有足夠的表達力,或者我們善於用這些語言表達意圖,就不會那麼需要註解,甚至根本不需要。
註解的恰當用途,是彌補我們未能在程式碼中表達清楚的內容……註解總是代表著失敗。我們無法總是找到不靠註解就表達清楚意圖的辦法,所以不得不寫註解,但這並不值得慶賀。
我同意,好的軟體設計可以減少對註解的需求,尤其是方法體中的註解。但註解並不代表失敗。註解提供的資訊與程式碼很不一樣,而且目前還無法用程式碼表達。程式碼和註解各有所長,都有重要價值。即使能設法將註解中的資訊表達在程式碼裡,也很難說這就是一種改進。
註解的目的之一,就是讓人不必閱讀程式碼。例如,開發人員無需閱讀整個方法體,只看一段簡短的介面註解,就能獲得呼叫該方法所需的全部資訊。Martin 的做法正好相反:他主張用程式碼替換註解。對於方法中的某個程式碼塊,Martin 建議不寫註解解釋它,而是將它提取為單獨的方法(不加註解),用方法名代替說明。這會產生很長的名稱,例如 isLeastRelevantMultipleOfNextLargerPrimeFactor。即使用了這麼多單字,這樣的名稱仍然晦澀,提供的資訊還不如一段好註解。而且,每次呼叫這個方法,開發人員都相當於把它的文件重新輸入了一遍!
我擔心 Martin 的設計哲學會助長一種不好的心態:程式設計師為了不被視為失敗者,刻意避免寫註解。這甚至可能讓優秀的設計者受到不公正的批評:“你的程式碼有什麼問題,竟然還需要註解?”
好的註解不是失敗。它們提升了程式碼的價值,並在定義抽象、管理系統複雜性方面發揮著基礎作用。