有什么比花時間寫注釋更令人感到興奮的事情嗎?如果我沒有猜錯,你可能會說:“不好意思,所有事情都比寫注釋更令人感到興奮”。如果有人要你給代碼加上注釋,對你來說就像是一種侮辱。你的代碼寫得如此優雅,它已經足以說明它要做的事情,注釋是多余的,代碼就是一切。
無論是開源項目還是專業軟件開發,代碼注釋通常有兩種形式:要么沒有和要么毫無用處。任何領域或使用任何編程語言的程序員,無論他們來自世界的哪個地方,似乎都不喜歡給代碼寫注釋。如果你想講故事,可能會選擇不同的人生道路(比如去當作家?)。
這種不情愿甚至形成了某種范式和哲學觀點,認為代碼注釋實際上是有害的,任何試圖逃避它的人現在都可以愉快地重新討論這些主張。但是,稍微夸張一點說,我們實際上是在用這種方式破壞信息。雖然代碼注釋有時候確實會適得其反,但真正有害的是我們對它的看法。
說到底,代碼注釋就像是錯誤處理,我們很早就被告知它很重要,但卻無法理解其中的原因。我們越來越厭煩為同樣的老師、主管或煩人的隊友做這件事。但就像錯誤處理一樣,如果做得好,我們就是從中獲益最多的人。但要做到這一點,我們需要面對一些嚴酷的事實,并承認根本不存在所謂的自解釋代碼。
所以,讓我們來戳破其中的一些泡沫吧!
自解釋的代碼是不存在的
反對給代碼寫注釋的人認為,“代碼應該好到不需要任何多余的解釋”。好的代碼確實不需要注釋來描述變量或函數是干什么用的。
// bad start:int a = 4 * OFFSET;// but don't use a comment to tell what it does:int a = 4 * OFFSET; // initial foo value// instead choose a name telling it itself:int initial_foo = 4 * OFFSET;
確實,有意義的變量名根本不需要注釋,但這實際上更像是一種體面的編碼風格,而不是文檔。當這種片面的觀點變成反對使用代碼注釋的普遍理由時,問題就出現了。
問題是,即使變量、方法、類、函數、模塊的名稱是自解釋的,但這些并不能描述出代碼的全局面貌,也不一定能說明各部分代碼為什么要那么寫。當然,清晰的實現往往會讓我們產生一種錯覺,認為不需要再寫注釋了。當你花了幾個小時甚至幾天時間解決了手頭的問題,那些代碼在當下可能是完美的,然后你把它們打包、提交。
但是一個月后會怎樣?你能記住多少細節?它們還是那么有意義嗎?
軟件開發困難重重
當然,有人可能會爭辯說:“代碼就在那里,你看一下就明白了”。如果我們說的是某塊代碼是干什么用的,那么或許這么說是有道理的。但對于任何超出這個范圍的東西,深挖代碼可能是在浪費時間,就像在閱讀一本沒有索引的書,你要從頭讀起,才可能找到你需要的東西。
而且,這不僅僅是為了了解別人的代碼,或者向別人解釋你的想法。當你重新查看舊代碼或者修復錯誤時,你的腦子里是不是經常犯嘀咕,或者因為 git blame 顯示了你的名字而感到驚訝?然而,再往后,它們可能被忘得一干二凈,然后你會再次相信一切都應該是自解釋的,所有的細節都應該是明確無誤的。
無論你怎么努力,軟件本身并不會完全自解釋。這既不是你的錯,我也不是想要質疑你的能力,這與人類本身有關,我們低估了軟件的復雜性,而且人類的思維具有波動性。注釋的目的不是為了指出代碼中存在的缺陷,而是為了抵制編程語言本身存在的缺點。即使是最干凈的代碼也不可能自己解釋寫代碼的人在寫代碼時在想些什么。有可能一切都是完美的,但仍然會出錯。注釋并不是干凈代碼的替代方法,而是代碼的固有組成部分。
代碼注釋解析
在進一步討論我們的問題之前,先讓我們來看看不同的代碼注釋風格。
/*** Javadoc-style documentation comment.*/void foo(void) {if (bar \u0026gt; 10) {/* regular comment */...}}
常規注釋就是編程語言本身定義的注釋。根據經驗,它們不應該被廣泛使用,因為它們傾向于用來解釋代碼在做什么。
另一方面,文檔注釋從外部角度描述了全局變量、函數和模塊。在函數體內部,它們基本上就是常規注釋,工具通常會忽略它們。如果在函數內部有一些值得描述的東西,看看是否可以把它們放進函數描述本身。
文檔注釋本質上就是常規注釋加上一些額外的附件,例如額外的正斜杠/// doc comment、感嘆號//! doc comment或者/*! multiline doc comment /,或者Javadoc注釋中的附加星號/* doc comment */。實際上,其他編程語言和工具也支持Javadoc,所以這里就以它為例子。
當然,你也可以使用常規注釋,并忘掉那些時髦的標簽。不過,一些文檔生成器(如 Doxygen 或 Sphinx)可以直接根據注釋創建 PDF、HTML 或手冊頁,大多數現代 IDE 為它們提供了額外的顯示支持,省得你老是進行上下文切換,而且還可以為你提供一些有用的信息。
除了注釋的后處理之外,注釋的格式并不重要,重要的是你想要表達什么。
冗余的注釋聚焦在錯誤的信息上
我們已經得出結論,即不應該記錄代碼在做什么,而是記錄為什么要這么做以及怎樣做,但這究竟意味著什么呢?
人們不喜歡寫注釋的一個常見原因是“它們只是在陳述已經很明顯的東西”,所以注釋是多余的。對于一般性的注釋,確實難以反駁,特別是在面向對象語言的封裝方面。一些簡單的函數,比如 get_temperature() 的一般性描述可能如下所示:
/*** Returns the temperature.*/int get_temperature(void) {return temperature;}
這里的注釋確實沒有增加太多的價值,它本質上只是重復了函數的名字,只是在說明這個函數的作用。這不是我們想要的,我們想要的是代碼沒有告訴我們的東西。
這個函數非常簡單,所以寫注釋是絕對沒有必要的。但話又說回來,軟件開發當中沒有什么東西是真正簡單的。如果你夠仔細,就會發現每個函數都有值得寫的東西,而這些東西并不能從它的名字甚至是簡單的一兩行代碼中看出來。
/*** Returns the temperature in tenth degrees Celsius* in range [0..1000], or -1 in case of an error.** The temperature itself is set in the periodically* executed read_temperature() function.** Make sure to call init_adc() before calling this* function here, or you will get undefined data.*/int get_temperature(void) {return temperature;}
事實證明,這個看似簡單的函數有很多額外的信息可以寫。如果只是看代碼,可能無法明顯地看出其中的信息,包括內部數據處理和程序流程。當然,深挖代碼最終會獲得同樣的信息,但這樣會浪費很多時間和腦力。
有人可能會說,我們沒辦法為這些實現細節寫注釋。為什么要這樣?為什么不詳細說明那些現細節,讓別人可以更容易地理解代碼在做什么?
每個函數都有自己的特點,至少會有一個細節、副作用、異常、限制,等等,它們都值得寫出來,這意味著你可能需要從不同的角度來看待這個函數,才能找出它們。為此,你不可避免地要沉浸在代碼隱藏的細節當中,這樣才可能發現一些之前沒有想到過的特殊情況。因此,代碼注釋不僅可以幫助讀代碼的人理解代碼,還能幫助寫代碼的人更好地了解代碼的內部細節。
如果你確實找不到有用的信息,那么應該問問自己為什么要寫這些代碼。這些代碼存在的理由是什么?而這些理由就是有用的信息。之前的例子也可以是這樣:
/*** Returns the temperature.** This is for testing purpose only and should* never be called from a real program.*/int get_temperature(void) {return temperature;}
請注意,這段代碼與之前完全相同,于是這又把我們引向了另一個問題“看似自解釋的代碼的注釋通常都很簡單”:它可能含糊不清,可能會導致錯誤的假設和潛在的缺陷。指出這些細節并消除潛在的歧義對于提升代碼質量來說至關重要,這說明注釋應該成為代碼的重要組成部分。
同樣,如果不深入研究代碼,就無法發現每個函數的特點。當然,在這些不起眼的細節中,總有一些比另外一些更值得我們注意,并不是說函數所涉及的東西都會很有趣。認知偏差的范圍很廣,有些東西在這個時刻對你來說是顯而易見的,并不意味著對于其他人來說也是這樣——包括未來的你。
讓注釋成為代碼的一部分
現在我們來看看另一個人們不喜歡寫代碼注釋的原因:當代碼發生改變時,注釋會過時。但其實這只是一個偷懶的借口,在寫代碼時通常不會考慮將來會不會再去修改代碼,一旦代碼被提交并合并,就是確定和完美的,并永遠保持原樣。
代碼注釋的另一個更大的問題是,它們被視為獨立于代碼的東西,完全與代碼相分離。但如果我們將其視為代碼的組成部分,或者一種補充實體,那么只要代碼發生變化就會很自然地去調整注釋。
打破惡性循環
沒有人喜歡糟糕的代碼注釋,但排斥寫注釋對解決這個問題并沒有任何幫助。修復開發人員和代碼注釋之間的不正常關系是改善這種狀況的唯一方法,而將注釋視為代碼的組成部分是改善這種關系的第一步。
毫無疑問,在形成這種思維方式之前需要進行練習。從長遠來看,這對提升代碼質量來說是有益而無害的。
英文原文:
https://hackaday.com/2019/03/05/good-code-documents-itself-and-other-hilarious-jokes-you-shouldnt-tell-yourself/