主頁 > 軟體設計 > 程式員的代碼注釋需要寫么?

程式員的代碼注釋需要寫么?

2021-11-03 08:45:12 軟體設計

“別給糟糕的代碼加注釋——重新寫吧,”—Brian W. Kernighan與P. J. Plaugher

什么也比不上放置良好的注釋來得有用,什么也不會比亂七八糟的注釋更有本事搞亂一個模塊,什么也不會比陳舊、提供錯誤資訊的注釋更有破壞性,

注釋并不像辛德勒的名單,它們并不“純然地好”,實際上,注釋最多也就是一種必須的惡,若編程語言足夠有表達力,或者我們長于用這些語言來表達意圖,就不那么需要注釋——也許根本不需要,

注釋的恰當用法是彌補我們在用代碼表達意圖時遭遇的失敗,注意,我用了“失敗”一詞,我是說真的,注釋總是一種失敗,我們總無法找到不用注釋就能表達自我的方法,所以總要有注釋,這并不值得慶賀,

如果你發現自己需要寫注釋,再想想看是否有辦法翻盤,用代碼來表達,每次用代碼表達,你都該夸獎一下自己,每次寫注釋,你都該做個鬼臉,感受自己在表達能力上的失敗,

我為什么要極力貶低注釋?因為注釋會撒謊,也不是說總是如此或有意如此,但出現得實在太頻繁,注釋存在的時間越久,就離其所描述的代碼越遠,越來越變得全然錯誤,原因很簡單,程式員不能堅持維護注釋,

代碼在變動,在演化,從這里移到那里,彼此分離、重造又合到一處,很不幸,注釋并不總是隨之變動——不能總是跟著走,注釋常常會與其所描述的代碼分隔開來,孑然飄零,越來越不準確,例如,看看以下注釋以及它本來要描述的代碼行變成了什么樣子:

MockRequest request;
private final String HTTP_DATE_REGEXP = 
 "[SMTWF][a-z]{2}\\,\\s[0-9]{2}\\s[JFMASOND][a-z]{2}\\s"+
 "[0-9]{4}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\sGMT";
private Response response;
private FitNesseContext context;
private FileResponder responder;
private Locale saveLocale;
// Example: "Tue, 02 Apr 2003 22:18:49 GMT"

在HTTP_DATE_REGEXP常量及其注釋之間,有可能插入其他物體變數,

程式員應當負責將注釋保持在可維護、有關聯、精確的高度,我同意這種說法,但我更主張把力氣用在寫清楚代碼上,直接保證無須撰寫注釋,

不準確的注釋要比沒注釋壞得多,它們滿口胡言,它們預期的東西永不能實作,它們設定了無需也不應再遵循的舊規則,

真實只在一處地方有:代碼,只有代碼能忠實地告訴你它做的事,那是唯一真正準確的資訊來源,所以,盡管有時也需要注釋,我們也該多花心思盡量減少注釋量,

1 注釋不能美化糟糕的代碼

寫注釋的常見動機之一是糟糕的代碼的存在,我們撰寫一個模塊,發現它令人困擾、亂七八糟,我們知道,它爛透了,我們告訴自己:“喔,最好寫點注釋!”不!最好是把代碼弄干凈!

帶有少量注釋的整潔而有表達力的代碼,要比帶有大量注釋的零碎而復雜的代碼像樣得多,與其花時間撰寫解釋你搞出的糟糕的代碼的注釋,不如花時間清潔那堆糟糕的代碼,

2 用代碼來闡述

有時,代碼本身不足以解釋其行為,不幸的是,許多程式員據此以為代碼很少——如果有的話——能做好解釋作業,這種觀點純屬錯誤,你愿意看到這個:

// Check to see if the employee is eligible for full benefits 
if ((employee.flags & HOURLY_FLAG) && 
   (employee.age > 65))

還是這個?

if (employee.isEligibleForFullBenefits())

只要想上那么幾秒鐘,就能用代碼解釋你大部分的意圖,很多時候,簡單到只需要創建一個描述與注釋所言同一事物的函式即可,

3 好注釋

有些注釋是必須的,也是有利的,來看看一些我認為值得寫的注釋,不過要記住,唯一真正好的注釋是你想辦法不去寫的注釋,

3.1 法律資訊

有時,公司代碼規范要求撰寫與法律有關的注釋,例如,著作權及著作權宣告就是必須和有理由在每個源檔案開頭注釋處放置的內容,

下例是我們在FitNesse專案每個源檔案開頭放置的標準注釋,我可以很開心地說,IDE自動卷起這些注釋,這樣就不會顯得凌亂了,

// Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
// Released under the terms of the GNU General Public License version 2 or later.

這類注釋不應是合同或法典,只要有可能,就指向一份標準許可或其他外部檔案,而不要把所有條款放到注釋中,

3.2 提供資訊的注釋

有時,用注釋來提供基本資訊也有其用處,例如,以下注釋解釋了某個抽象方法的回傳值:

// Returns an instance of the Responder being tested.
protected abstract Responder responderInstance();

這類注釋有時管用,但更好的方式是盡量利用函式名稱傳達資訊,比如,在本例中,只要把函式重新命名為responderBeingTested,注釋就是多余的了,

下例稍好一些:

// format matched kk:mm:ss EEE, MMM dd, yyyy
Pattern timeMatcher = Pattern.compile(
 "\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");

在本例中,注釋說明,該正則運算式意在匹配一個經由SimpleDateFormat.format函式利用特定格式字串格式化的時間和日期,同樣,如果把這段代碼移到某個轉換日期和時間格式的類中,就會更好、更清晰,而注釋也就變得多此一舉了,

3.3 對意圖的解釋

有時,注釋不僅提供了有關實作的有用資訊,而且還提供了某個決定后面的意圖,在下例中,我們看到注釋反映出來的一個有趣決定,在對比兩個物件時,作者決定將他的類放置在比其他東西更高的位置,

public int compareTo(Object o)
{
 if(o instanceof WikiPagePath)
 {
  WikiPagePath p = (WikiPagePath) o;
  String compressedName = StringUtil.join(names, "");
  String compressedArgumentName = StringUtil.join(p.names, "");
  return compressedName.compareTo(compressedArgumentName);
 }
 return 1; // we are greater because we are the right type.
}

下面的例子甚至更好,你也許不同意程式員給這個問題提供的解決方案,但至少你知道他想干什么,

public void testConcurrentAddWidgets() throws Exception {
 WidgetBuilder widgetBuilder = 
  new WidgetBuilder(new Class[]{BoldWidget.class});
 String text = "'''bold text'''";
 ParentWidget parent = 
  new BoldWidget(new MockWidgetRoot(), "'''bold text'''");
 AtomicBoolean failFlag = new AtomicBoolean();
 failFlag.set(false);

 //This is our best attempt to get a race condition 
 //by creating large number of threads.
 for (int i = 0; i < 25000; i++) {
  WidgetBuilderThread widgetBuilderThread = 
   new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
  Thread thread = new Thread(widgetBuilderThread);
  thread.start();
 }
 assertEquals(false, failFlag.get());
}

3.4 闡釋

有時,注釋把某些晦澀難明的引數或回傳值的意義翻譯為某種可讀形式,也會是有用的,通常,更好的方法是盡量讓引數或回傳值自身就足夠清楚;但如果引數或回傳值是某個標準庫的一部分,或是你不能修改的代碼,幫助闡釋其含義的代碼就會有用,

public void testCompareTo() throws Exception
{
 WikiPagePath a = PathParser.parse("PageA");
 WikiPagePath ab = PathParser.parse("PageA.PageB");
 WikiPagePath b = PathParser.parse("PageB");
 WikiPagePath aa = PathParser.parse("PageA.PageA");
 WikiPagePath bb = PathParser.parse("PageB.PageB");
 WikiPagePath ba = PathParser.parse("PageB.PageA");

 assertTrue(a.compareTo(a) == 0);  // a == a
 assertTrue(a.compareTo(b) != 0);  // a != b
 assertTrue(ab.compareTo(ab) == 0); // ab == ab
 assertTrue(a.compareTo(b) == -1);  // a < b
 assertTrue(aa.compareTo(ab) == -1); // aa < ab
 assertTrue(ba.compareTo(bb) == -1); // ba < bb
 assertTrue(b.compareTo(a) == 1);  // b > a
 assertTrue(ab.compareTo(aa) == 1); // ab > aa
 assertTrue(bb.compareTo(ba) == 1); // bb > ba
}

當然,這也會冒闡釋性注釋本身就不正確的風險,回頭看看上例,你會發現想要確認注釋的正確性有多難,這一方面說明了闡釋有多必要,另外也說明了它有風險,所以,在寫這類注釋之前,考慮一下是否還有更好的辦法,然后再加倍小心地確認注釋正確性,

3.5 警示

有時,用于警告其他程式員會出現某種后果的注釋也是有用的,例如,下面的注釋解釋了為什么要關閉某個特定的測驗用例:

// Don't run unless you
// have some time to kill. 
public void _testWithReallyBigFile()
{
 writeLinesToFile(10000000);

 response.setBody(testFile);
 response.readyToSend(this);
 String responseString = output.toString();
 assertSubString("Content-Length: 1000000000", responseString);
 assertTrue(bytesSent > 1000000000);
}

當然,如今我們多數會利用附上恰當解釋性字串的@Ignore屬性來關閉測驗用例,比如@Ignore("Takes too long to run[2]"),但在JUnit4之前的日子里,慣常的做法是在方法名前面加上下劃線,如果注釋足夠有說服力,就會很有用了,

這里有個更麻煩的例子:

public static SimpleDateFormat makeStandardHttpDateFormat()
{
 //SimpleDateFormat is not thread safe, 
 //so we need to create each instance independently.
 SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
 df.setTimeZone(TimeZone.getTimeZone("GMT"));
 return df;
}

你也許會抱怨說,還會有更好的解決方法,我大概會同意,不過上面的注釋絕對有道理存在,它能阻止某位急切的程式員以效率之名使用靜態初始器,

3.6 TODO注釋

有時,有理由用//TODO形式在源代碼中放置要做的作業串列,在下例中,TODO注釋解釋了為什么該函式的實作部分無所作為,將來應該是怎樣,

//TODO-MdM these are not needed
// We expect this to go away when we do the checkout model
protected VersionInfo makeVersion() throws Exception
{
return null;
}

TODO是一種程式員認為應該做,但由于某些原因目前還沒做的作業,它可能是要提醒洗掉某個不必要的特性,或者要求他人注意某個問題,它可能是懇請別人取個好名字,或者提示對依賴于某個計劃事件的修改,無論TODO的目的如何,它都不是在系統中留下糟糕的代碼的借口,

如今,大多數好IDE都提供了特別的手段來定位所有TODO注釋,這些注釋看來丟不了,你不會愿意代碼因為TODO的存在而變成一堆垃圾,所以要定期查看,洗掉不再需要的,

3.7 放大

注釋可以用來放大某種看來不合理之物的重要性,

String listItemContent = match.group(3).trim();
// the trim is real important. It removes the starting 
// spaces that could cause the item to be recognized
// as another list.
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));

3.8 公共API中的Javadoc

沒有什么比被良好描述的公共API更有用和令人滿意的了,標準Java庫中的Javadoc就是一例,沒有它們,寫Java程式就會變得很難,

如果你在撰寫公共API,就該為它撰寫良好的Javadoc,不過要記住本章中的其他建議,就像其他注釋一樣,Javadoc也可能誤導、不適用或者提供錯誤資訊,

4 壞注釋

大多數注釋都屬此類,通常,壞注釋都是糟糕的代碼的支撐或借口,或者對錯誤決策的修正,基本上等于程式員自說自話,

推薦你閱讀《代碼整潔之道》,第4章中有詳細講解,

本書大致可分為3個部分,前幾章介紹撰寫整潔代碼的原則、模式和實踐,這部分有相當多的示例代碼,讀起來頗具挑戰性,讀完這幾章,就為閱讀第2部分做好了準備,如果你就此止步,只能祝你好運啦!

第2部分最需要花工夫,這部分包括幾個復雜性不斷增加的案例研究,每個案例都清理一些代碼——把有問題的代碼轉化為問題少一些的代碼,這部分極為詳細,你的思維要在講解和代碼段之間跳來跳去,你得分析和理解那些代碼,琢磨每次修改的來龍去脈,

你付出的勞動將在第3部分得到回報,這部分只有一章,列出從上述案例研究中得到的啟示和靈感,在遍覽和清理案例中的代碼時,我們把每個操作理由記錄為一種啟示或靈感,我們嘗試去理解自己對閱讀和修改代碼的反應,盡力了解為什么會有這樣的感受、為什么會如此行事,結果得到了一套描述在撰寫、閱讀、清理代碼時思維方式的知識庫,

如果你在閱讀第2部分的案例研究時沒有好好用功,那么這套知識庫對你來說可能所值無幾,在這些案例研究中,每次修改都仔細注明了相關啟示的標號,這些標號用方括號標出,如:[H22],由此你可以看到這些啟示在何種環境下被應用和撰寫,啟示本身不值錢,啟示與案例研究中清理代碼的具體決策之間的關系才有價值,

如果你跳過案例研究部分,只閱讀了第1部分和第3部分,那就不過是又看了一本關于寫出好軟體的“感覺不錯”的書,但如果你肯花時間琢磨那些案例,亦步亦趨——站在作者的角度,迫使自己以作者的思維路徑考慮問題,就能更深刻地理解這些原則、模式、實踐和啟示,這樣的話,就像一個熟練地掌握了騎車的技術后,自行車就如同其身體的延伸部分那樣;對你來說,本書所介紹的整潔代碼的原則、模式、實踐和啟示就成為了本身具有的技藝,而不再是“感覺不錯”的知識,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/345809.html

標籤:其他

上一篇:一分鐘讓你了解芯片作業的心臟——晶振

下一篇:馬斯克回應被聯合國逼捐?發中文《七步詩》引熱議

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more