讀《編程匠藝—編寫卓越的代碼》:編寫代碼注釋

查爾斯.普雷斯特.斯科特(Charles Prestwich Scott):
評論是自由的,但事實是神圣的

注釋可以將優(yōu)秀的代碼和糟糕的代碼區(qū)分開來,將艱澀難懂的邏輯與清晰友好的算法區(qū)分開。但我們也無須過分夸大注釋的作用。如果說你的代碼是蛋糕,那么你的注釋就如同蛋糕上糖衣,它被精巧地涂抹以增加蛋糕的美感和價值,但不能掩飾蛋糕的裂縫和瑕疵。

一、什么是代碼注釋

  • 從語法角度看,注釋是編譯器將忽略不計的源代碼。
  • 從語義角度看,注釋是對其所處位置的代碼的注解。
    注釋的作用有多種:
    1) 強調(diào)某個特殊的問題領(lǐng)域;
    2)在頭文件中記錄媒介;
    3)描述某個算法,協(xié)助維護程序;
    4)分割各個函數(shù)以幫組快速瀏覽整個源文件。
  • 注釋是內(nèi)部的文檔化機制

二、注釋標(biāo)記

  • C語言的注/*開頭,*/結(jié)束,可以跨多行。
  • C++、C99、C#和Java語言增加了以//作為開頭的單行注釋。
  • 其他語言也提供了類似的塊注釋和行注釋功能,語法可能不同。

三、多少注釋是恰當(dāng)?shù)?/h3>

小威廉.斯特倫克(William Strunk Jr.):
鏗鏘有力之文必簡潔

  • 把重點放在注釋的質(zhì)量上,而不是數(shù)量上;比編寫多少注釋更重要的是注釋的內(nèi)容。
  • 只在能為代碼增加色彩時才編寫注釋。優(yōu)秀的演奏家追求用最少的演奏創(chuàng)造出最美的聲音。
  • 真正好的代碼并不需要注釋,它們能自我解釋。像f()g()這樣的函數(shù)名才會大聲嚷嚷,要求注釋說明它們,但是像someGoodExample()這樣的函數(shù)名根本不需要注釋。

四、注釋中應(yīng)該有什么

賀瑞斯(Horace):
寫好源頭和本源是明智的做法

不好的注釋比沒有注釋更糟糕,它們歪曲事實進而誤導(dǎo)讀者。

  1. 解釋為什么,而不是怎么樣
    (a).注釋不是用來描述程序是怎樣運行的,代碼自己是其最權(quán)威的描述。
    (b).注釋應(yīng)該描述代碼塊起什么作用。
    /* update WidgetList structure from GibWLRegistry*/這樣的注釋,不如寫/* cache widget information for later*/。注釋同一段代碼,后者表達(dá)代碼目的,而前者只告訴你這段代碼做了什么。
    (c).注釋可用來解釋為什么代碼要這么寫。如有兩種可供選擇的實現(xiàn)方式,你決定采用其中一種,也許你該使用注釋來解釋一下,為什么你選擇了這種實現(xiàn)方式。

  2. 不要取代或重復(fù)代碼
    (a).如果注釋描述的內(nèi)容可以由編程語言本身實現(xiàn),那么就試著用具體語句表達(dá)它。
    (b).如果你發(fā)現(xiàn)你用大量注釋解釋某個算法如何運行,請趕快停止,然后考慮是否需要重構(gòu)代碼或算法。
    (c).不要用注釋描述變量的用法,重命名變量。
    (d).如果你要文檔化一個應(yīng)當(dāng)總是成立的條件,也許你該寫一個斷言

  3. 確保注釋有用
    注釋的作用是標(biāo)注和解釋代碼。注釋需要滿足:
    (a).記錄意想不到的內(nèi)容。如果代碼有一部分是不常見、意想不到的,用注釋記錄下來;如果有特定問題需要規(guī)避,如一個操作系統(tǒng)問題,應(yīng)該在注釋中說明。
    (b).講真話。不要寫不準(zhǔn)確的注釋。
    (c).清晰明了。不要讓注釋模棱兩可,內(nèi)容盡量具體;不一定是完整的、語法正確的句子,但必須是可讀的;避免縮寫。
    (d).只關(guān)注當(dāng)前代碼,避免分心。不要記錄過去我們怎么做;不要把應(yīng)當(dāng)剔除的代碼(如舊代碼)包含在注釋中;避免使用ASCII藝術(shù);不要在代碼塊結(jié)尾添加多余的注釋,如在if語句的后括號后注釋//end if

每當(dāng)寫完注釋后,在代碼上下文中回顧一遍這些注釋,考慮一下:它們是否都是正確的信息,它們是否簡潔,它們是否容易理解。

五、實踐

看看下面一小段C++代碼:

for (int i = 0; i < wlst.sz(); ++i)
k(wlst[i])

很明顯,你根本搞不懂這是在干嗎。應(yīng)用合理排版規(guī)則和添加注釋說明對它進行改進:

// Iterate over all widgets in  the widget list
for (int i = 0; i < wlst.sz(); ++i)
{
        // print out this widget
        k(wlst[i]);
}

現(xiàn)在好多了,起碼這段代碼的目的清晰了。但它還是不太令人滿足。使用恰當(dāng)?shù)暮瘮?shù)名和變量重寫代碼:

for (int i = 0; i < widgets.size(); ++i)
       printWidget(widgets[i]);

再看代碼,發(fā)現(xiàn)根本不需要注釋,代碼本身能自我描述了。

這印證了:

不要文檔化差勁的代碼——重寫這些代碼

六、注釋的一些細(xì)節(jié)及其作用

根據(jù)你自己的品味,把下面的建議當(dāng)作指導(dǎo)性的原則:

  • 一致性。
    所有的注釋應(yīng)該清晰明了,前后一致。為你的注釋選擇一種特定的布局方式,始終堅持使用。

  • 清晰的塊注釋
    將首位標(biāo)記(如C/C++中/**/)各自放在一行,凸顯它們;讓塊注釋左側(cè)多縮進一個字符,讓注釋顯的像一個整體。如

/* 
   * This is 
   * a block
   * comment.
   */

而不是這種

  /* 
This is 
        a block
comment.
  */
  • 縮進的注釋
    注釋不應(yīng)該截斷代碼或打亂邏輯流程。讓注釋與對應(yīng)的代碼的縮進一樣。
    看到下面這樣的代碼,無疑更費勁:
void strangeCommentStyle()
{
        for (int i = 0; i < JUST_ENOUGH; ++i)
        {
        //This is a meaning comment about the next line.
                  doSomethingMeaningful(i);
   // good comment 
                  doOtherOperation(i);
        }
}     

在不帶括號的循環(huán)中,不要把注釋放在單循環(huán)體語句之前,這會導(dǎo)致災(zāi)難性后果。

  • 行尾注釋
    大多數(shù)注釋都是放在各自的行上,但有時較短的單行注釋可以跟在代碼語句后頭。這種情況下,在注釋與代碼之間留白是一種好習(xí)慣。例如:
Class Point
{
public:
        ........
private:
        int x;                 //X軸坐標(biāo)值
        int y;                 //Y軸坐標(biāo)值
        int z;                 //Z軸坐標(biāo)值
};

如果行尾注釋直接跟在聲明后面,代碼看起來會顯得擁擠,讀起來更費勁。

  • 選擇一種維護成本較低的風(fēng)格
    可以把更多時間放在編寫代碼而不是擺弄注釋上。
  • 幫組閱讀代碼
    a).注釋通常在它描述的代碼上方,而不是下方。讀者一般都是從上向下閱讀,這樣注釋可以讓讀者為即將讀到的內(nèi)容做好準(zhǔn)備。
    b).注釋后面緊跟代碼,代碼后面放一個空白行,接下來是下一個注釋代碼段。這種代碼與空白一起使用的方式,可以將代碼分隔成"段落",益于閱讀。
  • 分隔板
    注釋經(jīng)常被用作不同代碼部分之間的“分隔板”。如在一個若干個類或函數(shù)的源文件中,可能有以下注釋:
/******************************************
 * class foo implementation
******************************************/

////////////////////////////////////////////////////

我們應(yīng)當(dāng)避免大量使用這種"分隔板"類的注釋。將代碼分組的應(yīng)該是良好的縮進和結(jié)構(gòu),而不是生動的ASCII藝術(shù)。

  • 標(biāo)志
    注釋還可以用作代碼中內(nèi)嵌的標(biāo)志。如:
    a).//XXX用來標(biāo)識棘手的代碼或需要重新編寫的代碼;
    b).//FIXME表示已知已經(jīng)損壞的代碼;
    c).//TODO用來標(biāo)識缺少一些功能,需要日后返工。

  • 文件頭注釋
    a).所有源文件都應(yīng)該以描述其內(nèi)容的注釋塊作為開頭。這個頭注釋是一個概述前言,提供了文件的關(guān)鍵信息。
    b).頭注釋應(yīng)該包含文件的目的、版本、所有權(quán)及版權(quán)聲明;
    c).頭注釋不需要把文件中定義的所有函數(shù)、類、全局變量等內(nèi)容羅列,太累贅。

  • 幫組編寫程序
    a).一種常見的編碼方式是首先用注釋構(gòu)造代碼結(jié)構(gòu),然后在各行注釋下面填寫代碼。這種方式要注意及時刪除一些無用的注釋和修改一些不恰當(dāng)?shù)淖⑨尅?br> b).另一種常見的編碼方式是徒手編寫代碼,再添加注釋??赡艹3M浤稠椆ぷ骰虿荒軐懗龊玫淖⑨尅8玫姆绞绞沁厡懘a邊寫注釋,實踐會告訴你寫多少注釋。

  • 注釋過時
    要明白注釋會過時。當(dāng)你修正、添加或修改任何代碼時,請同時修正、添加或修改相應(yīng)的注釋。

八、總結(jié)

德爾莫.施瓦茨(Delmore Schwartz):
重要的寫作是要講眼之所見,這樣就不必一遍又一遍的口口相傳了

寫高質(zhì)量的注釋比寫高數(shù)量的注釋更好,然而,最好的是寫出高質(zhì)量的代碼——那些無需注釋的自文檔化代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容