一場(chǎng)關(guān)于代碼注釋的爭(zhēng)執(zhí),引發(fā)的三點(diǎn)思考
本文轉(zhuǎn)載自微信公眾號(hào)「架構(gòu)精進(jìn)之路」,作者架構(gòu)精進(jìn)之路。轉(zhuǎn)載本文請(qǐng)聯(lián)系架構(gòu)精進(jìn)之路公眾號(hào)。
在一次研發(fā)溝通會(huì)上,大家關(guān)于是否需要代碼注釋做了一番爭(zhēng)執(zhí)(討論)。
主要內(nèi)容簡(jiǎn)述如下:
A:我提議項(xiàng)目應(yīng)該有個(gè)注釋,我們有些程序員幾乎從不注釋代碼,誰都知道沒注釋的代碼是沒法閱讀的。
B:我覺得注釋沒必要,注釋被當(dāng)做萬靈藥,可是任何實(shí)際編碼過的人都知道,注釋反而會(huì)使代碼更難讀懂。注釋很容易產(chǎn)生大量的廢話,而編碼語言相對(duì)簡(jiǎn)明扼要得多。
C:是這么回事。假如代碼不清晰,又怎能注釋的清楚呢?再說,代碼一變,注釋就過時(shí)。要是誤讀了過時(shí)的注釋,可能又會(huì)踩坑了。
C 接著說:另外,注釋過多的代碼更難讀懂,這樣增大了閱讀量。已經(jīng)有一堆代碼要去讀了,何必再去讀一大堆注釋呢?
A:編輯器要知道的東西全在代碼中?二進(jìn)制文件里面嗎?爭(zhēng)論注釋有無價(jià)值干啥呢?
B:我反對(duì)注釋主要是覺得浪費(fèi)資源。
D:也不能這么說,注釋可能會(huì)被濫用,但是注釋用得好時(shí)卻妙不可言。另外,在我的工作經(jīng)歷中,有注釋和沒注釋的我都維護(hù)過,我個(gè)人還是更愿意維護(hù)有注釋的代碼。最后補(bǔ)一句:盡管沒必要制定注釋的標(biāo)準(zhǔn),但是我還是提倡大家注釋好自己的代碼。
........
關(guān)于是否加注釋爭(zhēng)執(zhí)討論比較久,最終大家統(tǒng)一了如下決定:
“提倡加注釋,但不能濫用。我們開發(fā)流程中會(huì)有Code Review過程,這樣每個(gè)人都將了解好的注釋是什么樣的,同時(shí)你遇到不好的代碼注釋,也需要告訴他如何改進(jìn)。”
問題思考
作為研發(fā)同學(xué),對(duì)于代碼“注釋”其實(shí)并不陌生。它往往作為我們代碼文檔的特殊補(bǔ)充而存在。
其實(shí)在代碼文檔中,起主要作用的因素并非注釋,而是好的編程風(fēng)格。
編程風(fēng)格包括:良好的程序結(jié)構(gòu)、易于理解的方法、有意義的變量名和子程序名、常量、清晰的布局,以及最低復(fù)雜度的控制流及數(shù)據(jù)結(jié)構(gòu)。
會(huì)后我就在反思:那注釋真的是以啰嗦的方式又重復(fù)一遍代碼,所以沒有用么?
好注釋可不是重復(fù)代碼或者解釋代碼,它會(huì)讓作者的意圖更清晰,注釋應(yīng)該能在更高的意圖上解釋你想干什么。
日常的注釋
一般情況下,注釋寫的糟糕很容易,寫的出色就很難了。注釋不好只會(huì)幫倒忙。
我們來看幾個(gè)例子:
- // write out the sums 1..n for all n from 1 to num
- current = 1;
- previous = 0;
- sum = 1;
- for(int i=0; i<num; i++){
- System.out.Println("Sum = " + sum);
- sum = current + previous;
- previous = current;
- current = sum;
- }
其實(shí)這段代碼計(jì)算的是斐波那契(Fibonacci)數(shù)列的前num個(gè)值。如果注釋錯(cuò)了,盲目相信注釋可能會(huì)南轅北轍,但是好的注釋會(huì)事半功倍。
- // compute the square root of num using the Newton-Raphson approximation
- r = num / 2;
- while(abs(r - (num/r) > TOLERANCE){
- r = 0.5 * (r + (num/r));
- }
- System.out.println("r = " + r);
上述例子,它用來計(jì)算num的平方根,代碼一般,但注釋比較精準(zhǔn)。
注釋的目的
寫代碼和注釋的第一目的是幫助人理解代碼,理解作者的意圖。
所以優(yōu)秀的代碼本身就有自說明功能,只有在代碼本身無法清晰地闡述作者的意圖時(shí),才考慮寫注釋。
即是:注釋應(yīng)該表達(dá)我的代碼為什么要這么做,而不是表達(dá)我的代碼做了什么。
我們軟件開發(fā)過程中引入了那么多的設(shè)計(jì)模式、框架、組件,開發(fā)過程制定了那么詳細(xì)的設(shè)計(jì)規(guī)范、編碼規(guī)范、命名規(guī)范、很大一部分原因就是為了提高代碼的可讀性。
編程語言特別是高級(jí)編程語言,本身就是人和機(jī)器之間溝通的語言,語言本身就要求滿足人的可讀性,需要用符合我們自然語言的表達(dá)習(xí)慣,不需要額外的注釋。
注釋怎么寫?
當(dāng)然,好代碼 > 差代碼+好注釋,好的注釋是很有價(jià)值的,壞注釋不僅浪費(fèi)時(shí)間還可能有害,自解釋的代碼最好。
當(dāng)然,好代碼 > 差代碼+好注釋,好的注釋是很有價(jià)值的,壞注釋不僅浪費(fèi)時(shí)間還可能有害,自解釋的代碼最好。好的注釋不是重復(fù)代碼或解釋它,而是使代碼更清楚,注釋在高于代碼的抽象水平上解釋代碼要做什么事。
具體的操作手段,包括但不限于以下幾點(diǎn):
- 適當(dāng)注釋,仔細(xì)衡量,不要隱晦也不要多余;
- 注意存在變更情況是,需要及時(shí)更新;
- 注釋代碼中一些tricky的技巧或者特殊的業(yè)務(wù)邏輯,否則會(huì)讓讀代碼的人摸不著頭腦;
- 如果附上jira、bug、需求等的地址能夠幫助理解代碼,可以適當(dāng)加上;
- 如果代碼命名良好,結(jié)構(gòu)合理,一般來說是不需要什么注釋的。但是用一句話解釋下意圖和功能也是極好的,因?yàn)楹芏鄷r(shí)候僅僅是想知道代碼怎么用,讀一句注釋要比分析幾十行代碼快得多。
注釋的原則
1)寫注釋應(yīng)遵循奧卡姆剃刀原則:如無必要,勿增實(shí)體
注釋寫的不好、維護(hù)得不好(比如改了代碼沒改注釋)會(huì)導(dǎo)致代碼的可讀性變差。
2)有句話叫“代碼即注釋”,雖然不完全是,但有道理的
把代碼寫好、寫漂亮,注釋就可以精煉,也必然能寫得更易懂。此外,把思路(難的、關(guān)鍵的)寫清楚,比啥Author、Date重要多了。抓重要信息。
3)建議注釋里盡量寫為什么,而不是做了什么
做了什么,看代碼就好,代碼不會(huì)騙人。但為什么要寫成這樣,有時(shí)候就非常讓人困惑。有可能是處理某個(gè)corner case,有可能是繞過某個(gè)系統(tǒng)限制,也可能是什么奇葩需求,這種代碼,沒有當(dāng)時(shí)的 context,過幾個(gè)月看,像甲骨文一樣,不知道是想干什么。再有看不順眼來優(yōu)化一下,以后就不知道哪個(gè)地方會(huì)崩了。
其實(shí),大部分的代碼應(yīng)當(dāng)是不言自明的,不需要注釋的。
總結(jié)
- 好的注釋才有價(jià)值
該不該注釋是個(gè)需要認(rèn)真對(duì)待的問題。差勁的注釋只會(huì)浪費(fèi)時(shí)間。好的注釋才有價(jià)值。注釋的位置可以在:變量特定的含義和限制、某個(gè)職責(zé)代碼塊的開始、一般控制結(jié)構(gòu)的開始、子程序調(diào)用處、方法開始處描述功能、類開始處描述功能。
- 源代碼應(yīng)當(dāng)含有程序大部分的關(guān)鍵信息。
只要程序依然在用,源代碼比其他資料都能保持更新,故而將重要信息融入代碼是很有好處的。
- 好代碼本身就是最好的說明
如果代碼太糟,需要大量注釋,應(yīng)先試著改進(jìn)代碼,直至無須過多注釋為止。
- 注釋應(yīng)說出代碼無法說出的東西
例如概述或用意等信息。注釋本身應(yīng)該包含的是對(duì)代碼的簡(jiǎn)潔的抽象概括,而不是具體代碼的實(shí)現(xiàn)細(xì)節(jié)。
- 注釋風(fēng)格也應(yīng)該簡(jiǎn)潔易于維護(hù)
有的注釋風(fēng)格需要許多重復(fù)性勞動(dòng),應(yīng)舍棄之,改用易于維護(hù)的注釋風(fēng)格。
作者:架構(gòu)精進(jìn)之路,專注軟件架構(gòu)研究,技術(shù)學(xué)習(xí)與個(gè)人成長(zhǎng)。