【翻譯】編寫代碼註釋的最佳實踐

著名的麻省理工學院教授哈爾-艾貝爾森(Hal Abelson)曾說過: 代碼首先是寫給人看的,只是計算機拿去運行了而已。html

雖然他可能故意的低估了計算機運行代碼的重要性,但他說的是很是正確的。咱們的成型有兩個很是不一樣的受衆。編譯器和解釋器不會關注代碼的註釋,對於計算機來講,全部語法正確的程序都是一樣的容易理解的。而對於閱讀代碼的人來講,則徹底是不同的。咱們發現有些的程序代碼很是難以理解,咱們但願經過增長註釋來幫咱們閱讀。node

有不少資源能夠幫助程序員們寫出更好的代碼,例如圖書文檔和靜態代碼分析工具等。可是如何才能寫出更好的代碼註釋的資源卻不多。雖然,咱們能夠很容易的度量程序中註釋的數量,但很難去度量其質量,並且這二者之間也不存在必然的聯繫。一個糟糕的註釋比沒有註釋更加糟糕。git

正如Peter Vogel所寫的:程序員

•編寫和維護註釋是一項有開支工做。•你的編譯器並不檢查你的註釋,因此計算機沒有辦法肯定註釋是否正確而有效。•另外一方面,你能夠保證計算機徹底按照你的代碼的要求來運行。編程

雖然全部這些觀點都是正確的,但若是走到另外一個極端:歷來不寫註釋,也是一個錯誤。json

這裏有一些基本的規則,能夠幫助你提高寫註釋的能力:app

1.規則1:註釋不該該與代碼重複;2.規則2:好的註釋不能做爲代碼不清晰的藉口;3.規則3:若是你不能寫出一個清晰的註釋,那麼你的代碼可能也是有問題的;4.規則4:註釋應該消除混亂,而不是製造混亂;5.規則5:在註釋中解釋不規範的代碼;6.規則6:提供複製的代碼的原始來源的連接;7.規則7:最最有幫助的地方加入外部參考資料的連接;8.規則8:在修復bug時添加註釋;9.規則9:使用註釋來標記不完整的實現;less

下面是對以上9條規則的詳細說明,以及結合具體的案例,來闡釋如何在實際編碼中應用這些規則。ide

規則1:註釋不該該與代碼重複

許多初級程序員在代碼中寫了太多的註釋。由於他們在初學代碼是被老師訓練成這樣。工具

例如不少人在每一個閉合的大括號上都加上一行註釋,以代表那個代碼塊要結束。

if (x > 3) {…} // if

還有更嚴重的要求,在每行代碼上都要加上註釋。雖然這對初學者來講多是一個有效的措施,但這樣的註釋習慣,就像孩子學習騎自行車的輔助輪同樣,是最終須要放棄的。

不能增長任何信息的註釋是負面價值的東西,應爲他們:

•增長了視覺混亂;•浪費了讀寫的時間;•可能會過期;

典型的一個壞列子以下:

i = i + 1;         // Add one to i

註釋不附加任何有效的信息,但產生了維護成本。

要求對每行代碼都寫註釋的規則,在Reddit上受到了嘲諷:

// create a for loop // <-- commentfor // start for loop(   // round bracket// newlineint // type for declarationi    // name for declaration=   // assignment operator for declaration0   // start value for i
規則2:好的註釋不能做爲代碼不清晰的藉口

註釋的另一個被誤用,就是提供了本應該在代碼中出現的信息。一個簡單的例子,有人用一個字母來命名一個變量,而後給變量添加一個註釋來描述變量的用途:

private static Node getBestChildNode(Node node) {Node n; // best child node candidatefor (Node node: node.getChildren()) {// update n if the current state is betterif (n == null || utility(node) > utility(n)) {n = node;}}return n;}

其實,更好的變量命名能夠消除對註釋的須要

private static Node getBestChildNode(Node node) {Node bestNode;for (Node currentNode: node.getChildren()) {if (bestNode == null || utility(currentNode) > utility(bestNode)) {bestNode = currentNode;}}return bestNode;}

正如 Kernighan和 Plauger 在《編程風格的要素》中寫道:"不要註釋壞的代碼,而是重寫"。

規則3:若是你不能寫出一個清晰的註釋,那麼你的代碼可能也是有問題的

在Unix源代碼中最臭名昭著的註釋是:你不該該理解這一點。她出如今一些毛茸茸的上下文切換代碼以前。丹尼斯·裏奇 (Dennis Ritchie) 後來解釋說,它的目的是「本着‘這不會出如今考試中’的精神,而不是無禮的挑戰。」 不幸的是,事實證實,他和合著者肯·湯普森 (Ken Thompson) 本身並不理解,後來不得不重寫。

這讓人想起克尼漢定律

調試一段代碼的難度是編寫它們的兩倍,所以若是你的代碼寫的儘量巧妙,按照定義而言,你可能沒有能力來調試它了。

警告閱讀者遠離你的代碼,就像打開你的汽車的危險信號燈:認可你正在作的事情是非法的。相反,將代碼重寫爲你能很好理解並易解釋的,最好是簡單直接的。

規則4:註釋應該消除混亂,而不是製造混亂

若是沒有史蒂文·列維的《***:計算機革命的英雄》中的這段故事,關於壞註釋的討論就不完整了:

[Peter Samson] 拒絕在他的源代碼中添加註釋來解釋他在特定時間所作的事情,這一點尤爲晦澀。Samson 編寫的一個分佈普遍的程序繼續執行數百條彙編語言指令,在包含數字 1750 的指令旁邊只有一個註釋。註釋是 RIPJSB,人們絞盡腦汁思考它的含義,直到有人發現 1750 是巴赫去世的那一年,而Samson寫的是Rest In Peace Johann Sebastian Bach的縮寫。

雖然我和其餘人同樣的欣賞一個好***,但這不是典範。若是你的註釋引發了混亂,而不是消除混亂,那就請刪除它吧。

規則5:在註釋中解釋不規範的代碼

對其餘人可能認爲不須要或者多餘的代碼進行註釋是一個好主意,例如來自App Inventor 的代碼:

final Object value = (new JSONTokener(jsonString)).nextValue();// Note that JSONTokener.nextValue() may return// a value equals() to null.if (value == null || value.equals(null)) {return null;}

若是沒有註釋,有人可能會簡化代碼,或者將其視爲神祕但必不可少的咒語。經過寫下爲何須要好代碼來節省將來閱讀者的時間和焦慮。

須要判斷代碼是否須要註釋,在學習Kotlin的時候,遇到過一個Android教程中的代碼,形式以下:

if (b == true)

我立刻想到是否能夠替換爲:

if (b)

就像在 Java 中所作的那樣。通過一些研究,我瞭解到可空布爾變量明確地與 true 進行比較,以免醜陋的空檢查:

if (b != null && b)

所以,我建議不要對常見習語的去寫註釋,除非是專門爲新手編寫的教程。

規則6:提供複製的代碼的原始來源的連接

若是你像大多數程序員同樣,有時會使用在網上找到的代碼。包括對來源的引用使將來的讀者可以得到完整的上下文,例如:

•正在解決什麼問題•誰提供了代碼•爲何推薦該解決方案•評論者是怎麼想的•它是否仍然有效•如何改進

例如,請考慮如下注釋:

/** Converts a Drawable to Bitmap. via https://stackoverflow.com/a/46018816/2219998. */

按照註釋中連接中信息能夠看出:

•該代碼的做者是Tomáš Procházka,他在Stack Overflow上排名前3%。•一個評論者提供了一個優化方法,已經被歸入到repo中。•另外一個評論者提出了一個避免邊緣狀況的方法。

與此註釋造成鮮明對比的是(稍做改動,爲保護犯錯者):

// Magical formula taken from a stackoverflow post, reputedly related to// human vision perception.return (int) (0.3 * red + 0.59 * green + 0.11 * blue);

任何想要了解上面代碼的人都將不得不去搜索查找公式。粘貼 URL 比稍後查找引用要快得多。

一些程序員可能不肯意代表他們沒有本身編寫代碼,但重用代碼多是一個明智的舉動,既節省了時間,又讓你得到了更多關注。固然,你永遠不該該粘貼您不理解的代碼。

人們從 Stack Overflow 問題和答案中複製了大量代碼。該代碼屬於須要署名的知識共享許可。引用註釋就知足該要求。

一樣地,你應該參考那些有幫助的教程,以即可以再次找到它們,並感謝他們的做者:

// Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html// for a great reference and examples.
規則7:最最有幫助的地方加入外部參考資料的連接

固然,並不是全部引用都指向了 Stack Overflow,能夠考慮:

// http://tools.ietf.org/html/rfc4180 suggests that CSV lines// should be terminated by CRLF, hence the \r\n.csvStringBuilder.append("\r\n");

到標準和其餘文檔的連接能夠幫助讀者理解你的代碼正在解決的問題。雖然這些信息可能會出如今設計文件中,但恰當的註釋會在什麼時候何地提供給讀者最須要的信息。在這種狀況下,跟隨連接代表RFC 4180已經被RFC 7111更新,這是有用的信息。

規則8:在修復bug時添加註釋

不只應該在最初編寫代碼時添加註釋,還應該在修改代碼時添加註釋,尤爲是在修復錯誤時。考慮這個註釋:

// NOTE: At least in Firefox 2, if the user drags outside of the browser window,// mouse-move (and even mouse-down) events will not be received until// the user drags back inside the window. A workaround for this issue// exists in the implementation for onMouseLeave().@Overridepublic void onMouseMove(Widget sender, int x, int y) { .. }

註釋不只幫助讀者理解當前和引用的方法中的代碼,還有助於肯定是否仍然須要該代碼以及如何測試它。

也能夠幫助問題修復的跟進:

// Use the name as the title if the properties did not include one (issue #1425)

雖然git blame可用於查找添加或修改行的提交,但提交消息每每很簡短,而且最重要的更改(例如,修復問題 #1425)可能不是最近提交的一部分(例如,移動從一個文件到另外一個文件的方法)。

規則9:使用註釋來標記不完整的實現

有時即便代碼有已知的缺陷,也有必要簽入代碼。雖然不共享代碼中已知的缺陷可能很誘人,但最好使這些缺陷明確,例如使用 TODO 註釋:

// TODO(hal): We are making the decimal separator be a period,// regardless of the locale of the phone. We need to think about// how to allow comma as decimal separator, which will require// updating number parsing and other places that transform numbers// to strings, such as FormatAsDecimal

對此類註釋使用標準格式有助於衡量和解決技術債務。更好的是,向你的跟進列表中添加一個問題,並在你的註釋中引用該問題。

結論

我但願上面的例子已經代表註釋不會成爲錯誤代碼的藉口或修復;它們經過提供不一樣類型的信息來補充良好的代碼。正如 Stack Overflow 聯合創始人 Jeff Atwood 所寫,「代碼告訴你如何,註釋告訴你爲何。

遵循這些規則應該能夠節省你和你的隊友的時間和挫折感。

最後,我確信這些規則並不是詳盡無遺,並期待在評論中看到更多的建議和補充。

原文:https://stackoverflow.blog/2021/07/05/best-practices-for-writing-code-comments/

相關文章
相關標籤/搜索