日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

代码质量随想录(五)注得多不如注得巧

發(fā)布時間:2025/3/17 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 代码质量随想录(五)注得多不如注得巧 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

  寫代碼也流行注水了么?不是不是,我說的是注釋。其實注釋這個東西,歷史久遠。我們可以寬泛一點兒說,《春秋》就是要配上左傳的注解,才能興發(fā)其“微言大義”嘛!注釋有很多種,如果按照注釋者與原文作者是不是同一個人來分,可以劃分成自注和他注。在程序員這個行當內,一般來說,還是自注多一些,自己寫代碼,自己加注。有的時候進行代碼審查或者復用遺留代碼時,才可能會有必要對他人寫的代碼加注。

  從代碼質量的角度看,注釋寫得應不應該,寫得好不好,應該從它是否有助于加深代碼讀者及代碼使用者對程序的理解這一標準來判斷。按照《The Art of Readable Code》作者的說法,注釋的目標,就是讓讀者盡量明白代碼作者的編程意圖

  那么,具體到代碼書寫層面,究竟怎么注釋才算好呢?這個問題得展開來談。這一篇文章先談談注釋的時機問題,下一篇再來研究注釋的內容。

1.顯而易見的代碼別注釋

  寫注釋經常會遭遇兩種極端態(tài)度,一種是絕對不寫注釋,一種是寫廢話連篇的注釋。對于持第一種態(tài)度的人,小翔希望看完講注釋的這兩篇文章之后,能夠適當轉變一下態(tài)度,稍稍緩釋惜墨如金的執(zhí)念,多為大家?guī)硪恍┚实淖⑨尅S泻芏嗬碛啥紩荒脕頌椴粚懽⑨屪鲛q護,這在后文會一一講到,我在這里主要是想先說說口水型注釋的害處。從我個人的工作經歷來看,不寫注釋的人一旦能夠理性地認識到注釋的好處,那么他們很有可能養(yǎng)成在編碼的同時自發(fā)地為代碼精準加注的好習慣,然而沒話找話型的程序員,則很難寫出優(yōu)雅簡潔的注釋來,對這些人來說,先要消解注釋泡沫才行。

  比如,代碼本身就含有的題中之義就不宜再以注釋的形式重復了。

  • //?Account類的定義。?
  • class?Account?{?
  • ??//?構造器?
  • ??public?Account(){...}?
  • ?
  • ??//?將profit字段設定為新指定的值?
  • ??public?void?setProfit(double?profit){...}?
  • ?
  • ??//?獲取本Account對象的profit字段值?
  • ??public?double?getProfit(){...}?
  • }?
  •   以上幾行注釋的內容完全是在重述代碼,意義不大

    2.注釋要盡量闡發(fā)被注標識符無法容納的意思,比如操作的同步性、工作流程、參數(shù)的范圍、返回值、異常等有價值的信息

      形成上例這種情況,也許還有一個原因,那就是有些公司或者團隊會對注釋形成一種強制要求,比如在Java語言中要求公有和保護級別的API必須寫Javadoc。這種規(guī)范是好的,不過要定出具體細則來,比如類的總結部分怎么寫,構建子怎么寫注釋,簡單的setter/getter方法怎么寫注釋。

      針對上述這些問題,我覺得在制定開發(fā)團隊的注釋規(guī)范時,要明確指出:注釋應該盡量闡明被注標識符無法容納的義涵。例如,針對本類字段的簡單存取方法,如果其中有特殊之處,比如setter方法參數(shù)的取值范圍、參數(shù)非法時是否會造成異常、設置的新值是否立刻生效等等問題,那么這些情況就應當明確標注。例如:

  • /**??
  • ?*?將profit字段設定為新指定的值。設置動作有可能不會立即生效,要根據(jù)該賬戶對象的修改?
  • ?*?策略所允許的單位時段內最大修改次數(shù)來定。如果修改策略是“延時生效”,則超過修改次數(shù)????
  • ?*?限制的修改動作會在下個時間段生效.?
  • ?*?@param?profit?新的收益率,必須在[0.0d,?1.0d]之間?
  • ?*?@throws?IllegalArgumentException??如果收益率不在合法區(qū)間內?
  • ?*?@throws?IllegalOperationException?如果本次設置已在修改策略容許次數(shù)之外,?
  • ?*???????????????????????????????????且修改策略是“立即生效”?
  • ?*/?
  • public?void?setProfit(double?profit){...}?
  •   雖然有點兒啰嗦(我寫注釋的毛病,哈哈),不過比起上例來說,畢竟還是帶來了一些新內容。而且一旦通過注釋把這些隱晦的東西挑明了,那么還可以由此引發(fā)新的討論,以促進團隊成員對代碼的理解,進而觸發(fā)重構。比如大家可以盡情吐槽:這個方法名怎么能簡簡單單地叫成setProfit呢?這樣怎么能體現(xiàn)出它還受制于“賬戶修改策略”這個事實?參數(shù)怎么能叫成profit?為什么不寫成profitBetweenZeroAndOne?如果設置無法立刻生效的話,那為什么不提供通知機制?不然客戶代碼怎么知道什么時候才能設置生效?等等等等……這些質疑未必各個都有道理,不過可以由此讓我們重新審視該方法,甚至是整個類,看看它設計得是不是有問題,對下游開發(fā)者是否友好。

      再看getProfit方法,可就有點兒尷尬了,因為不管怎么寫注釋,貌似都很無力。這時咱們就可以很有自信地無視它了。不過使用Eclipse的開發(fā)者可能會遇到一些小障礙,比如在設定里面設置好了強制要求所有protected、public的API都要寫Javadoc注釋,那么略去這種getProfit方法不注,可能會有警告或者錯誤。這種小麻煩,恐怕就需要一些變通辦法了,大家如果有好辦法,也請告訴我。

      如果代碼讀者和下游開發(fā)者有必要適當?shù)夭t解工作流程和返回值詳情,那么這些信息就要注釋,比如:

  • //?在子樹中尋找某個深度范圍內,具有給定名稱的節(jié)點。?
  • public?Node?findNodeInSubtree(Node?subtree,?string?name,?int?depth){...}?
  •   就應該改為:

  • //?找尋具有指定名稱的節(jié)點,找不到則返回null。?
  • //?如果深度值小于等于0,則整個子樹都將被查找。?
  • //?如果大于0,則只在N級深度范圍內查找。?
  • public?Node?findNodeInSubtree(Node?subtree,?string?name,?int?depth){...}?
  • 3.如果編程意圖不夠明顯,則可以適當?shù)丶有┳⑨尅4朔N情況的根本解決辦法還是通過重構來理順復雜的代碼,使之清晰、直觀

  • #?移除第二個'*'字符及其后內容?
  • name?=?'*'.join(line.split('*')[:2])?
  •   ARC作者可能認為以上這句大家看到之后第一眼有點搞不清楚狀況,所以建議加上那行注釋。小翔倒是覺得,不妨對上面的代碼進行重構,將“切割、數(shù)組切片、拼合”這個大操作拆解成三個小操作,并且封裝起來,這樣更符合迪米特原則(又叫得墨忒耳定律、最少知識原則),而且看上去代碼會更加清晰,不需加注即可明白。

  • String?name=truncateFromDelimiter(line,'*',2);?
  • ...?
  • private?String?truncateFromDelimiter(String?input,?char?delimiter,??
  • ?????????????????????????????????????int?groupIndexToDropFrom){...}?
  • 4.再好的注釋也無法徹底掩飾壞名稱

  • //?確保回覆對象的內容符合請求對象中關于條目數(shù)量、總字節(jié)數(shù)等規(guī)格的限定。?
  • public?void?cleanReply(Request?request,?Reply?reply){...}?
  •   以上注釋中的“確保”(Enforce)、“限定”(Limit)等詞應該直接納入方法名稱中。不妨改成:

  • //?經請求對象所限定的規(guī)格包括“條目數(shù)量”、“總字節(jié)數(shù)”等指標。?
  • public?void?enforceLimitsFromRequest(Request?request,?Reply?reply){...}?
  •   這樣不僅注釋內容變簡單了,而且方法名稱所表達的意思也比原來精確許多,讓人更易理解。關于這一點,我在做項目時體會特別深刻,千萬不要試圖用注釋去粉飾糟糕的名字,而應該直接修正不當?shù)拿?/strong>。

  • //?釋放主鍵所指向的注冊表操作句柄。該方法并不修改實際的注冊表內容。?
  • public?void?deleteRegistry(RegistryKey?key);?
  •   既然“并不修改實際的注冊表內容”,那么名稱中delete何謂?用注釋無法掩飾這個矛盾。莫如去掉注釋,直書其意,這樣不需要注釋大家也能從方法名稱中準確判斷出該操作的效果僅僅是釋放句柄:

  • public?void?releaseRegistryHandle(RegistryKey?key);?
  • 5.能夠對代碼讀者起到警示、啟發(fā)或備忘作用的注釋值得去寫

      有時需要警告同組開發(fā)者,不要進行倉促的優(yōu)化:

  • //?在處理該數(shù)據(jù)時,使用二叉樹比哈希表快40%,計算哈希碼的開銷比進行左右比較的開銷要大。?
  •   有時則要避免開發(fā)者在無關緊要的問題上浪費時間:

  • //?這種試探法可能會漏掉一些詞語,不過不影響使用,100%解決這個問題很難。?
  •   有時陳述將來可改觀之處:

  • //?這個類很亂,也許應該創(chuàng)建一個ResourceNode子類來下移一部分代碼。?
  • //?TODO:應該使用更快的算法?
  •   有時要陳述不完備的功能:

  • //?TODO:?除了JPEG之外,還得處理其他格式。?
  •   上述最后兩種情況要特別注意,也就是在注釋待改進或者功能不完備的代碼時,強烈建議使用特殊的前導標識符來標明注釋行。這樣可以藉助文本統(tǒng)計或者IDE提供的待辦任務視圖來立刻檢索到項目中存在的隱患,促進開發(fā)者之間對代碼現(xiàn)狀的理解,以便發(fā)現(xiàn)問題及時溝通。這種注釋其實扮演了“待辦任務”或“待辦事項”的角色。咱們業(yè)內通用的標注法按照緊急程度從低到高排列如下,新入行的小朋友們可以學習一下:

  • //?TODO:?可改觀或不完備的功能。?
  • //?HACK:??用來應急的雜技代碼,稍后必須糾正。?
  • //?FIXME:?代碼有錯,需要修正。?
  • //?XXX:?????代碼大誤,即行修正!?
  • 6.關乎代碼邏輯的常量,如其名稱不足以描述其包含的重要信息,則必須加注

      必須具備某種特性,方能使程序正常運轉的常量應該加注,例如:

  • /**?只要不小于處理器數(shù)量的2倍就好.?*/?
  • public?static?final?int?NUM_THREADS?=?8;?
  •   翔按:ARC作者在說明此種情況應當加注時,舉了上面這個例子。其實,這里不妨補以// TODO: 提示信息,因為這種“不小于處理器數(shù)量的2倍”的特性可能會隨著運行環(huán)境的改變而無法滿足。僅憑這個注釋,程序員未必能在出問題時第一時間就定位到該常量。大家可以在遇到這種情況時,補以提示性注釋,例如“// TODO: 在后續(xù)版本改進過程中,應使用系統(tǒng)硬件信息來初始化此常量值,不宜手工指定”。

      隨意選取數(shù)值的限定常量亦應加注,以便后續(xù)版本要對其進行可定制的功能擴展時參考(注意TODO后面的話):

  • //?TODO:?如果將來要由客戶自行指定訂閱點上限,則可把此值改為變量。?
  • /**?最大的RSS訂閱點數(shù)量。這么多訂閱點足以應對客戶當前的需求了.?*/?
  • public?static?final?int?MAX_RSS_SUBSCRIPTIONS?=?1000;?
  •   精心調優(yōu)后的常量應加注,避免誤調

  • //?使用0.72作為質量參數(shù),可以在畫質與占用空間之間取得良好平衡。?
  • public?static?final?double?IMAGE_QUALITY?=?0.72d;?
  •   其實這一條原則的三個小分支,都與上一條所述的“能夠對代碼讀者起到警示、啟發(fā)或備忘作用的注釋值得去寫”這一原則有重復。之所以要單列出來,是因為常量的設置尤為微妙,經常會暗含無法用標識符全面涵蓋的細微特征,應當適時地輔以注釋。

    7.提高注釋質量所奉行的原則之一與提高代碼質量的大原則一致:用局外人的視點來審讀代碼

      這一點,我在日常編碼中曾一再對身邊同事強調,此時不妨再啰嗦幾句。那就是要從當前代碼中跳出來,“冷眼看程序,熱心挑毛病”

      大部分人不甚明瞭的微妙語言細節(jié)應該加注,例如:

  • struct?Recorder?{?
  • ??vector?data;?
  • ??...?
  • ??void?Clear()?{?
  • ????vector().swap(data);
  • ??}?
  • };?
  •   如果誰突然闖進來看到上面的代碼,肯定第一個就要問:為什么不直接調用data.clean()函數(shù)呢?與其讓讀者陷入猜測與不解之中,咱們不如直接用注釋把隱晦的細節(jié)說明白了:

  • //?在vector對象上進行強制內存回收,參見“STL容器的swap技巧”(STL?swap?trick)?
  • vector().swap(data);?
  •   好久沒做C++的項目了,剛Google了一下,這個技巧問的人還蠻多,我想起當時Scott Meyers在《Effective STL》一書里面講過,Stack Overflow上面有人說是條目17,大家可以去復習一下。我覺得,如果真是像本例這種情況,某段代碼使用了一個不成文的高端技巧或者某權威著作中深入講述的代碼慣用法,那么不如在注釋中直接給出明確的參考源,例如“參閱網址:……;參考書目或文章:……”

      可能會導致客戶代碼出狀況的API要加注。例如:

  • //?調用外部程序投遞郵件(有可能耗時長達1分鐘,若屆時還未完成,則算超時)?
  • public?void?sendEmail(String?to,?String?subject,?String?body){...}?
  • ?
  • //?算法時間復雜度是O(標簽數(shù)量*平均標簽深度),若輸入數(shù)據(jù)含有大量嵌套錯誤,可能相當耗時。?
  • public?void?fixBrokenHtml(String?html){...}?
  •   類之間的互動、整個系統(tǒng)數(shù)據(jù)流、程序的入口點等宏觀信息應該加注。講到這個問題時,ARC的作者讓我們假想一下,如果某個程序狼(或者程序娘,原文按照英語慣例,寫的是her)突然闖入團隊里面,你怎么以代碼的方式向他解釋整個項目的架構,使他盡速融入開發(fā)過程中呢?這個時候就必須有一些全局性的注釋了,通過閱讀這些注釋,新人就可以迅速把握住整個項目的大方向、大節(jié)奏。例如:

  • //?在業(yè)務邏輯與數(shù)據(jù)庫層之間的粘合代碼,應用程序不直接使用它。?
  • //?該類內部邏輯稍顯復雜,不過僅僅扮演智能緩存池的角色。它并不依賴于系統(tǒng)的其他部分。?
  •   在Java項目中,我們通常以包注釋或類概覽的Javadoc形式來提供宏觀注釋。

  • /**??
  • ?*?為便于訪問與文件操作有關的功能而提供的工具類。?
  • ?*?其內部會處理與操作權限等事項相關的細節(jié)問題。??
  • ?*/??
  • public?class?FileMiscellaneousUtility{...}
  • 8.以注釋將長段代碼分為小段,使讀者快速掌握程序流程

      在上一篇文章中舉過一個類似的例子,那次是編寫一個社交軟件中的潛在友人推薦功能。那個例子其實只有8行有效代碼。所以只需分段,不用注釋,讀者就可以清晰地理解它。然而有的時候,如果某方法內部包含數(shù)十甚至上百行代碼,而因為效率或復雜度等原因無法立刻進行代碼整理的話,那么可以先寫一些注釋來厘清程序流程,這樣也便于后續(xù)的維護。例如:

  • public?void??generateUserReport{?
  • ??//?獲取配給該用戶的鎖?
  • ??...?
  • ??//?從數(shù)據(jù)源讀入用戶信息?
  • ??...?
  • ??//?將信息寫入文件?
  • ??...?
  • ??//?釋放用戶鎖?
  • ??...?
  • }?
  •   本來上述方法的四段應該分別被重構提取到四個不同的小方法之內,不過如果由于內部邏輯過于復雜,提取小方法的時候需要提取過多的參數(shù)以配合程序流程,那么在短期內無法進行有效重構的情況下,方法內部的適當注釋可以起到“起、承、轉、合”之目的,也可以為稍后進行重構的人厘清思路。

      嗯,這一篇講的心得有點多,可以小小總結一下。有一種傳統(tǒng)的說法,那就是“只注釋寫代碼的原因(why),不要注釋代碼具體內容(what)以及代碼的算法(how)”。不過看了上述這些例子之后,我想大家應該明白,有些時候,代碼的具體細節(jié)以及算法等內容,如果與代碼的理解緊密相關,那么就應該毫不吝惜地注釋。

      巧妙的注釋,好就好在它能促進代碼理解這一點上。不僅能讓讀者快速抓住代碼的意圖,而且還能為將來潛在的重構打開思路,同時還利于項目的維護,再有就是方便下游開發(fā)者進行二次開發(fā)。相反,對代碼理解毫無益處的注釋,就顯得笨拙、累贅,應該刪去。所以嘛,我想大家可以稍微修正一下上述說法了:只要有助于代碼的理解,“做什么、為什么做、怎么做“這幾方面都應加注。

      最后說一個小問題,那就是“注釋恐懼癥”。本文開頭說道,有些人不愿意寫注釋,原因有很多種。其中有一種就是注釋恐懼癥,一旦形成這個習慣,同時又沒有督促因素的話,則很難改正。此時如果通過團隊注釋規(guī)范強迫開發(fā)者去寫注釋的話,那么在沒有養(yǎng)成良好注釋習慣的情況下,就很可能會立刻走入另一個極端,為了應付差事而寫出毫無意義甚至刻意掩蓋代碼隱患的注釋來。對于如何克服注釋恐懼癥的問題,ARC的作者說了一個方法,我轉述給大家聽聽。他們二位建議,將自己的第一感覺以“原生態(tài)”的方式寫出來,例如:

  • //?額滴神啊,如果列表中有重復元素的話,這家伙就玩兒不轉了。??
  • //?(其實,ARC這本書的原文是這樣的:)??
  • //?Oh?crap,?this?stuff?will?get?tricky?
  • //?if?there?are?ever?duplicates?in?this?list.??
  •   上面這種話我估計人人都會寫吧。好,寫完了之后,用具體的、精確的詞語代替模糊的、情緒化的描述。

    • “額滴神啊”這幾個字,其實是想說“這里有必須要注意的狀況發(fā)生”。
    • “這家伙”其實指的是“處理輸入數(shù)據(jù)的代碼”。
    • “玩兒不轉了”意思是“這種情況下的算法很難實現(xiàn)”。

    ?

      所以,上述注釋經過美化之后,就變成了:

  • //?注意:這段代碼并不能處理含有重復元素的列表,因為那種情況下的算法太難實現(xiàn)了。?
  • //?(ARC的原文是:)?
  • //?Careful:?this?code?doesn't?handle?duplicates?in?the?list?
  • //?(because?that's?hard?to?do)?
  •   不知道上面這個頑皮搞笑的過程能不能克服注釋恐懼癥,如果不能的話,大家也可以跟帖想想辦法。

      這段時間一直沒有寫文章,一來由于工作繁忙,二來是晚上想貪玩看看比賽,三嘛,你別說,還真有可能是寫作恐懼癥呢!其實這更像是寫作倦怠癥。好了,不管怎么說,這次寫開了,就不倦怠了。這一篇講的是注釋的時機問題,也就是什么時候應該注釋,什么時候不該注釋,下一篇來講講內容問題,也就是說,如果要寫注釋的話,怎么寫才算好。

    愛飛翔

    2012年6月16日至17日

    本文使用Creative Commons BY-NC-ND 3.0協(xié)議(創(chuàng)作共用 自由轉載-保持署名-非商業(yè)使用-禁止衍生)發(fā)布。

    原文網址:http://agilemobidev.net/eastarlee/code-quality/think_in_code_quality_5_judicious_comments_zh_cn/

    轉載于:https://blog.51cto.com/eastarlee/901168

    總結

    以上是生活随笔為你收集整理的代码质量随想录(五)注得多不如注得巧的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。