喜歡寫(xiě)注釋嗎?喜歡寫(xiě),你就輸了
關(guān)注▲?W3Cschool▲?,每天一篇文章,與你共同成長(zhǎng)

作者丨Tameem Iftikhar
翻譯丨平川
策劃丨Tina
我并不是提倡不寫(xiě)代碼注釋,只是建議不要過(guò)于依賴注釋,這樣可以使代碼更干凈、更有表現(xiàn)力,這也能提高開(kāi)發(fā)人員的水平。我自己也在尋求編寫(xiě)更簡(jiǎn)潔的代碼,我盡力不編寫(xiě)糟糕的注釋,并在可能時(shí)重構(gòu)代碼。
這篇文章的標(biāo)題可能會(huì)讓你情緒激動(dòng),但請(qǐng)先耐心聽(tīng)我說(shuō)完。在適當(dāng)?shù)奈恢脤?xiě)下適當(dāng)?shù)淖⑨尶赡芊浅S杏?,但是沒(méi)有什么比無(wú)用的注釋更讓代碼混亂了。在某些情況下,我敢說(shuō),注釋可以彌補(bǔ)我們?cè)诖a中沒(méi)有完全表達(dá)出來(lái)的意思。因此,寫(xiě)注釋不值得贊美,而是應(yīng)該停下來(lái)問(wèn)問(wèn)自己,是否有更好的方式可以用代碼來(lái)表達(dá)自己。

帶有少量注釋的清晰而富于表現(xiàn)力的代碼,要比帶有大量注釋的混亂而復(fù)雜的代碼好得多。如果你已經(jīng)把代碼弄得一團(tuán)糟,不要花時(shí)間寫(xiě)注釋來(lái)解釋,而是要花時(shí)間梳理代碼。如果每次寫(xiě)注釋的時(shí)候,你都冥思苦想,覺(jué)得自己的表達(dá)能力不足,那么最終你就會(huì)寫(xiě)出簡(jiǎn)潔明了的代碼,完全沒(méi)有必要寫(xiě)注釋。鼓勵(lì)自己用代碼表達(dá)。
為什么對(duì)注釋如此不屑?
因?yàn)樗鼈儠?huì)說(shuō)謊,還會(huì)把代碼弄得亂七八糟。雖說(shuō)并非總是如此,也并非有意如此,但卻經(jīng)常如此。糟糕的代碼和帶有大量注釋的代碼之間有很高的相關(guān)性。注釋存在的時(shí)間越久就越容易偏離它們所描述的代碼——在某些情況下,它們可能是完全錯(cuò)誤的。實(shí)際上,隨著代碼庫(kù)和團(tuán)隊(duì)的增長(zhǎng),維護(hù)注釋成了不可能的事情。
注釋不同于《辛德勒的名單》。它們不是“純善的”。事實(shí)上,注釋充其量是一種必要的惡?!猂obert C.Martin
當(dāng)談?wù)撽P(guān)于注釋的話題時(shí),很重要的一點(diǎn)是,我們要看一下什么是恰當(dāng)?shù)淖⑨?,什么是糟糕的注釋,這樣我們才能學(xué)會(huì)寫(xiě)更好的注釋,或者完全避免注釋。
恰當(dāng)?shù)淖⑨?/span>
并不是所有的注釋都是不好的——有些注釋實(shí)際上非常必要。
出于法律目的的注釋
有時(shí)候,你可能需要出于法律目的編寫(xiě)特定的注釋,比如開(kāi)源項(xiàng)目的創(chuàng)作許可。一些現(xiàn)代化的 IDE 和文本編輯器會(huì)自動(dòng)將它們折疊起來(lái),保持工作區(qū)的整潔。
魔術(shù)表達(dá)式
如果你有一個(gè)復(fù)雜的 SQL 或正則表達(dá)式,它以神奇的方式做了一些令人興奮的事,那么請(qǐng)務(wù)必注釋,以便讓讀者更容易理解,因?yàn)槲覀兌疾皇?Regex 忍者。
//?匹配電子郵件地址的正則表達(dá)式
var?re?=?/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return?re.test(String(email).toLowerCase());
//?注意:添加一個(gè)富于表現(xiàn)力的函數(shù)名,注釋就變得沒(méi)有必要了
function?validateEmail(email)?{
????var?re?=?/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
????return?re.test(String(email).toLowerCase());
}
說(shuō)明意圖
在某些情況下,注釋有助于解釋決策或特定解決方案背后的意圖。例如,測(cè)試套件中的一條注釋告訴我們添加這行代碼是為了降低死鎖的幾率。
for?x?in?range(1,?500):
??#?這是運(yùn)行多個(gè)并行測(cè)試時(shí)預(yù)防死鎖的最好方法
??time.sleep(0.5)
??runTest(x)
??
結(jié)果預(yù)警
用注釋說(shuō)明代碼可能會(huì)產(chǎn)生嚴(yán)重的或可怕的后果,甚至鼓勵(lì)這樣做。在本例中,開(kāi)發(fā)人員讓讀者知道,當(dāng)與回調(diào)函數(shù)一起使用時(shí),QT 函數(shù)不是線程安全的。一般來(lái)說(shuō),如果一條注釋可以避免某個(gè)人在編程時(shí)陷入絕望,那么它就是有用的。
"""
??許多 Qt 函數(shù)都不是線程安全的。如果你使用回調(diào)函數(shù),
??即使你在所有繪制調(diào)用代碼的周?chē)技由湘i,你也會(huì)遇到段錯(cuò)誤,
??因?yàn)?Qt 的主事件循環(huán)仍在運(yùn)行,并且使用了沒(méi)加鎖的資源。
"""
from?multiprocessing.pool?import?ThreadPool
import?sys
from?threading?import?Lock
import?time
from?PyQt5?import?QtCore,?QtWidgets
class?Task(QtCore.QObject):
????updated?=?QtCore.pyqtSignal(int,?int)
????...............
????...............
????
TODO 注釋
這些注釋可以幫助我們標(biāo)記那些我們認(rèn)為應(yīng)該做,但是由于某些原因沒(méi)有做到的事情。它可能會(huì)提醒你刪除廢棄的特性,或者請(qǐng)求其他人查看某個(gè)問(wèn)題。它可能是要求其他人想一個(gè)更好的名字,或者是提醒他們根據(jù)計(jì)劃事件做出修改。
請(qǐng)記住,TODO 注釋不是在系統(tǒng)中留下糟糕代碼的借口。本質(zhì)上,每一行代碼都是一種負(fù)擔(dān)——最安全、最快的代碼是根本沒(méi)有代碼。
現(xiàn)在,大多數(shù)優(yōu)秀的 IDE 都提供了特殊的指令和特性來(lái)定位所有的 TODO 注釋,所以不太可能漏掉它們。盡管如此,你也不希望代碼中到處都是 TODO。所以要經(jīng)常瀏覽一下,刪除那些你能刪除的。
糟糕的注釋
這個(gè)清單比較長(zhǎng),但在本節(jié)中,我們將看到一些更為老生常談而又隨處可見(jiàn)的注釋。
明知故問(wèn)的注釋
有些注釋的意思顯而易見(jiàn),即它們沒(méi)有增加任何實(shí)際的價(jià)值,而且大多是噪音。
下面是一個(gè)開(kāi)源項(xiàng)目的代碼片段,其中包含大量明知故問(wèn)型的注釋,這些注釋使代碼變得混亂而晦澀。它們所提供的信息并不比代碼本身多,而且在某些情況下,閱讀注釋的時(shí)間甚至比閱讀代碼長(zhǎng)。
/**
*?與該容器相關(guān)的集群
*/
protected?Cluster?cluster?=?null;
/**
*?人類可讀的容器名
*/
protected?String?name?=?null;
/**
*?該容器的父容器
*/
protected?Container?parent?=?null;
/**
*?創(chuàng)建一個(gè)?Loader?配置父類加載器
*/
protected?ClassLoader?parentClassLoader?=?null;
不清不楚的注釋
如果你寫(xiě)注釋是為了符合公司規(guī)定,或者你只是覺(jué)得有必要添加一些注釋,那么你在注釋時(shí)就不會(huì)進(jìn)行適當(dāng)?shù)乃伎?。所以,如果你真的?xiě)了一條注釋,花點(diǎn)時(shí)間讓它對(duì)閱讀它的人有所幫助。
def?load_config():
??try:
????do_useful_stuff()
??except?Exception?as?ex:
????#?如有異常,退回到默認(rèn)狀態(tài)。
在這個(gè)例子中,作者想要傳達(dá)一些有關(guān)異常情況的重要信息。但這條注釋沒(méi)能解釋清楚我們將退回到什么樣的默認(rèn)狀態(tài)。如果一條注釋要求我們轉(zhuǎn)到另一個(gè)模塊來(lái)找出默認(rèn)值,那么它就沒(méi)有發(fā)揮應(yīng)有的作用。
注釋掉代碼
在團(tuán)隊(duì)準(zhǔn)備好刪除代碼之前先將其注釋掉似乎是一個(gè)好主意,但是不要這樣做。注釋代碼是一種弊端,團(tuán)隊(duì)中的其他成員不會(huì)刪除它,因?yàn)樗麄儠?huì)認(rèn)為它很重要。我們不是都在使用源碼控制嗎?所以我們不需要保留舊的代碼。我們可以跳到任何我們想要的版本。
噪音注釋
有些注釋毫無(wú)意義,純粹是噪音。時(shí)間久了,我們的大腦就會(huì)走馬觀花,我們也會(huì)開(kāi)始跳過(guò)那些需要注意的重要注釋??紤]一下下面的例子,其中的注釋提供了很多價(jià)值嗎?
-----------------------------
#?Exhibit?A
#?默認(rèn)構(gòu)造函數(shù)
def?get_todays_date():
??return?date.today()
-----------------------------
#?Exhibit?B
#?返回月份的天
#?@return:?月份的天
def?get_day_of_month()
??return?day_of_month
用編寫(xiě)干凈代碼的決心取代制造噪音的誘惑,你將成為一個(gè)更好、更快樂(lè)的程序員。
強(qiáng)制性注釋
這肯定會(huì)引起爭(zhēng)議。如果規(guī)定每個(gè)函數(shù)都需要一個(gè) Java 文檔或 Python docstring,是不是有點(diǎn)傻?大多數(shù)時(shí)候,類或函數(shù)名已經(jīng)告訴我們注釋所描述的內(nèi)容,它們是多余的。在這個(gè)例子中,注釋的數(shù)量比代碼的數(shù)量還多——這讓我很惱火。
class?ComplexNumber:
????"""
????這是一個(gè)用于復(fù)數(shù)的數(shù)學(xué)運(yùn)算類。
????屬性:
??????? real (int):復(fù)數(shù)的實(shí)部。
??????? imag (int):復(fù)數(shù)的虛部。
????"""
????def?__init__(self,?real,?imag):
????????"""
??????? ComplexNumber 類的構(gòu)造函數(shù)。
????????參數(shù):
?????????? real (int):復(fù)數(shù)的實(shí)部。
?????????? imag (int):復(fù)數(shù)的虛部。
????????"""
????def?add(self,?num):
????????"""
????????該函數(shù)用于復(fù)數(shù)求和。
????????參數(shù):
??????????? num (ComplexNumber):要加的復(fù)數(shù)。
????????返回值:
??????????? ComplexNumber:包含和的復(fù)數(shù)。
????????"""
????????re?=?self.real?+?num.real
????????im?=?self.imag?+?num.imag
????????return?ComplexNumber(re,?im)
help(ComplexNumber)??#?訪問(wèn)類的?docstring
help(ComplexNumber.add)??#?訪問(wèn)方法的?docstring
使用好的函數(shù)名或變量名
你可以使用更具表達(dá)性的函數(shù)和變量名替換注釋,從而使代碼更簡(jiǎn)潔??紤]下面的例子,第一個(gè)例子中的注釋就變得沒(méi)有必要了,因?yàn)橛幸粋€(gè)更好的函數(shù)名可以準(zhǔn)確地告訴讀者這個(gè)函數(shù)做了什么。
#?檢查日期是否是過(guò)去的日期
def?check_date(date):
???if?date??????return?true
???return?false
def?is_past_date(date):
???if?date??????return?true
???return?false
???
注釋不能彌補(bǔ)代碼的糟糕
編寫(xiě)注釋有一個(gè)比較常見(jiàn)的原因是糟糕的代碼。我們以前都見(jiàn)過(guò)這種情況,在某種程度上,我們自己也犯過(guò)這樣的錯(cuò)誤。我們寫(xiě)一個(gè)模塊或類,我們心里知道它混亂而無(wú)序。我們知道它一團(tuán)糟。所以我們對(duì)自己說(shuō),“哦,我最好加下注釋!”不!你最好把代碼梳理清楚!
/*
這段代碼糟透了。我知道,你知道,每個(gè)人都知道。
我們假裝什么都沒(méi)發(fā)生,然后繼續(xù)前進(jìn)。以后你叫我白癡好了。
*/
小 ? ?結(jié)
我并不是提倡不寫(xiě)代碼注釋,只是建議不要過(guò)于依賴注釋,這樣可以使代碼更干凈、更有表現(xiàn)力,這也能提高開(kāi)發(fā)人員的水平。我自己也在尋求編寫(xiě)更簡(jiǎn)潔的代碼,我盡力不編寫(xiě)糟糕的注釋,并在可能時(shí)重構(gòu)代碼——將我的代碼從宜家的一幅畫(huà)變成梵高的作品。
所以讓我們約法三章,不要寫(xiě)這么多注釋。
延伸閱讀:
https://levelup.gitconnected.com/every-time-you-comment-code-youve-already-failed-6fa9773b080f
-End-
編程獅(W3Cschool)
學(xué)編程,從W3Cschool開(kāi)始
微信掃描二維碼,關(guān)注公眾號(hào)
