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