如何編寫簡潔代碼?(下)
作者 | 袁慎建
接上篇《??如何編寫簡潔代碼?(上)??》
代碼不講真話的直接后果是所有人被誤導(dǎo)了,然后做了一件錯(cuò)誤的事情,不自知地將錯(cuò)就錯(cuò),讓錯(cuò)誤越陷越深,最后浪費(fèi)寶貴的時(shí)間??刹恢v真話,編寫代碼的人又不是故意的,也萬萬不可上綱上線,袁帥秉著內(nèi)訓(xùn)師作為知識沉淀者和文化傳播者角色的原則,借助教育代碼的機(jī)會組織了一次部門內(nèi)部的閑聊會,并美其名曰:Clean Code交流會。
講真話了嗎?
周五,是一個(gè)心情放松的日子,距離年會過去也快整整一周了。袁帥也趁此機(jī)會召集了團(tuán)隊(duì)的幾名開發(fā)在線學(xué)習(xí)系統(tǒng)的小伙伴來碼聊??雌饋硎且淮尾唤?jīng)意的安排,其實(shí)他早有準(zhǔn)備。
偽修飾
會議室里,袁帥開門見山:“最近跟一個(gè)北美 BP 在協(xié)商給他們大客戶團(tuán)隊(duì)的骨干成員強(qiáng)化敏捷工程實(shí)踐,現(xiàn)在很多部門內(nèi)訓(xùn)有一套敏捷工程實(shí)踐課程。但是很有意思的是,這個(gè)BP跟我線上會議溝通時(shí),從來不一步到位,總會在微信上再跟我發(fā)文字再解釋一遍,也不知道是我看著傻,還是她認(rèn)為我癡呆。”
大家被袁帥這開場給逗樂了,拋給他好奇的目光。
“沒講清楚唄,再給你講一遍?!鼻鍝P(yáng)一副若無其事的樣子。
“嗯,她擔(dān)怕開會沒講清楚,想再跟我解釋一遍,而且每次微信上的留言比她會議上講的要更明確更具體。所以,后來慢慢地我習(xí)慣在會議上忽視她提供的信息,直接看她的微信留言?!?/p>
“那你們還干嘛開會呀,不是白費(fèi)時(shí)間嗎!” 萬正義直率地補(bǔ)充道?!翱墒?,有時(shí)候她在后續(xù)的會議上又把之前留言的內(nèi)容給更新了卻忘了再給我留言?!?袁帥表現(xiàn)得無辜。
“所以,你仍然按照微信留言的信息,結(jié)果誤解了真實(shí)的意思!” 清揚(yáng)有搶答道。
“嗐,你還別提,我最近都沒跟活人打交道,也總是遇到這種煩惱?!?劉歡歡一臉滿腹牢騷的表情。
“是嘛,說來聽聽?”袁帥故作驚訝般試探道。
“注釋啊,最近看到一些代碼的注釋,本來不看注釋花點(diǎn)時(shí)間琢磨琢磨代碼,還能搞清楚代碼在做什么,可是有時(shí)想走捷徑,瞅了眼注釋,發(fā)現(xiàn)注釋是錯(cuò)的,騙的我團(tuán)團(tuán)轉(zhuǎn),被謊言帶節(jié)奏真不爽!” 歡歡打趣地補(bǔ)充。
“對的,這就跟我提到的那個(gè)BP類似,總習(xí)慣加一些修飾,本來初心是好的,是想解釋得更清楚,可是修飾只是一種補(bǔ)充信息,有時(shí)候原始信息變更了,修飾卻沒有跟著更新,不但沒有起到修飾作用,反而會誤導(dǎo)別人,很耽誤事兒!” 袁帥同情地回復(fù)了正義。
“說白了,還是沒有想清楚,代碼要做什么事情,然后覺得需要通過注釋去解釋一下,后面代碼改了,大概率是不會修改注釋的?!?正義總是話里充滿著正義。
“可能是使用中文注釋讀起來更順暢吧,因?yàn)榇a不太好用中文命名”。程曉娜以理解的口吻弱弱地補(bǔ)充了一句。
袁帥突然陷入了沉思,腦海里浮現(xiàn)出兩個(gè)畫面:
- 他曾經(jīng)輔導(dǎo)過的某些程序員,因?yàn)橛⒄Z功底不好,很難將中文翻譯成恰當(dāng)?shù)挠⑽?,這在一定程度限制了他們,即便有翻譯工具的輔助。
- 有一次為了給一個(gè)方法起名字,他跟三個(gè)10多年的工作經(jīng)驗(yàn)的技術(shù)Leader一起討論了10來分鐘,最后才搞定,但大家很開心。
“為什么要寫注釋啊?代碼自解釋不香嗎?只有代碼沒法自解釋的時(shí)候才用一下注釋還能接受!” 正義越發(fā)正義起來。
“可有時(shí)方法太長了,做的事情比較多,每一段相關(guān)的代碼用注釋說明一下在做什么也不是不可取的吧?”清揚(yáng)見縫插針地拋了個(gè)問題。
“Talk is cheap,show me the code!”袁帥展示了準(zhǔn)備好的代碼:
“2分鐘時(shí)間,大家先仔細(xì)閱讀這幾段段代碼,把你看到的問題和建議改進(jìn)措施發(fā)到咱們的討論群里哈?!?/p>
5分鐘后,袁帥把所有人的答案匯總起來:
- 示例1:類上的注釋完全沒必要,因?yàn)閂CS工具能夠很好地做記錄。
- 示例1:構(gòu)造方法方法上的注釋是冗余的,構(gòu)造器本身就能表達(dá)構(gòu)造對象,參數(shù)也能表達(dá)傳入的東西。
示例2:方法的注釋的確起到了解釋作用,但是讓方法自解釋更香:更名為listByStatus后干掉注釋。
緊接著,袁帥再拋出一個(gè)復(fù)雜的示例:
大家花了3分鐘找出問題:
- 示例3:方法太長,中間分段注釋解釋做什么的可以抽取方法,然后將注釋轉(zhuǎn)換成方法名來表達(dá)意圖。
- 示例3:printReceipt方法內(nèi)打印日期和客戶忠誠度注釋過期了,實(shí)際上代碼被注釋掉了。
- 示例3:printReceipt方法內(nèi)那個(gè)稅率寫的是15%,而實(shí)際用的10%,誤導(dǎo)讀者。
“注釋并不是一無是處,畢竟那誰講過 -- 「存在即合理」。注釋存在這么多年,而且很多地方咱們也看到過。” 袁帥又恢復(fù)了主持人的狀態(tài)。
“有啊,「開放API文檔」到處都是API注釋,而且還要寫好看了?!?/p>
“最近有幾段代碼實(shí)在有些實(shí)現(xiàn)的難言之隱,我必須通過注釋來解釋一下?!?/p>
“嗯,我剛碰到過「法律版本信息」的一些注釋,白紙黑字的注釋聲明不能少?!?/p>
“魔術(shù)代碼可以注釋一下啊,比如復(fù)雜的郵件正則表達(dá)式:
”
袁帥對最后這一個(gè)補(bǔ)充有點(diǎn)感興趣,趁機(jī)提了個(gè)問題:“魔術(shù)代碼除了注釋,還有更好的方式嗎?”
“引入「解釋性變量」啊,比如:emailMatcher?!鼻鍝P(yáng)這次臨場發(fā)揮反應(yīng)很快。
“大家怎么看待「TODO | FIXME」這種注釋呢?”清揚(yáng)緊接著有拋了個(gè)問題。
“不提倡,雖然它能幫助我們在本地記錄一些代辦列表,但盡可能及時(shí)處理完這些任務(wù),將注釋清理掉,別提交到生產(chǎn)上。”正義的聲音覆蓋了全場。
袁帥朝清揚(yáng)和正義點(diǎn)了個(gè)大拇指贊,簡單做了個(gè)總結(jié):“有一些場景需要注釋的,當(dāng)我們想要添加注釋的時(shí)候,我們首先應(yīng)該想想代碼能否做到自解釋?”
在收拾電腦之際,他腦海里浮現(xiàn)出Uncle Bob關(guān)于注釋的一句話:
“注釋不同于《辛德勒的名單》。它們不是‘純善的’。事實(shí)上,注釋充其量是一種必要的惡?!?/p>
-- Robert C.Martin
行外話
老馬(Martin Fowler)的博客上有一句話:“There are only two hard things in Computer Science: cache invalidation and naming things.”。這句話不是老馬本人講的,老馬表示很認(rèn)同,袁帥也很認(rèn)同,而且越來越信。
距離上一次的 Clean Code 交流會過去剛好一周,今天天氣格外晴朗,清揚(yáng)饒有興致喊上袁帥去樓底下新開的小餐館炒倆菜。
兩人來到餐廳剛坐定,袁帥手機(jī)微信語音響起來,是隔壁團(tuán)隊(duì)的 TL 石彪,想約他聊聊上次TDD工作坊后大家的落地情況和疑惑。
清揚(yáng)伸手遞過菜單示意袁帥點(diǎn)個(gè)菜,他只說了個(gè)“青菜”繼續(xù)跟石彪聊了起來。清揚(yáng)找了好一會也沒找到“青菜”,見袁帥聊得投入,就幫他點(diǎn)了個(gè)綠色的白灼生菜,還點(diǎn)了只清蒸桂花魚,外加兩份單人份的土雞湯和米飯。
10分鐘后,“青菜” 上來了,袁帥吃了兩口覺著不對勁:“咦,我點(diǎn)的是青菜啊,怎么上了個(gè)生菜呢?”
“大哥,我盡力了,菜單沒有找到“青菜”,就點(diǎn)了個(gè)這咯。” 清揚(yáng)一臉無辜。
“哦,我們那有一種叫“上海青”的蔬菜,平時(shí)我們都叫青菜?!?袁帥說完立即意識到自己現(xiàn)在身在北方,這邊的叫法跟老家可能不一樣。
“哈哈,我們西安把青菜理解成綠色的蔬菜~” 清揚(yáng)說著就遞過來菜單。
看到 “清炒油菜”,袁帥心里開始琢磨著:“不同地區(qū)(上下文),對同一個(gè)東西的叫法是可能不一樣的,如果切換了地區(qū),自己還沿用原來地區(qū)的叫法,很可能造成困惑和誤解。”
“不同行業(yè),不同領(lǐng)域,不同上下文,同物可不同名。你常常給我講寫代碼的時(shí)候要特別注意這一點(diǎn),開發(fā)哪個(gè)行業(yè)的系統(tǒng),就應(yīng)該使用該行業(yè)的業(yè)務(wù)語言,有利于統(tǒng)一語言,交流起來效率會高很多,而且代碼跟業(yè)務(wù)相匹配,更容易理解?!?清揚(yáng)猜到了袁帥在琢磨什么,替他做了一個(gè)總結(jié)。
“你們的清蒸桂花魚,請慢用!” 服務(wù)員把香氣四溢的魚端到桌上,這是袁帥小時(shí)候特別喜歡的一道菜,他開心地拿起筷子正要去夾魚尾:“咦,剛才服務(wù)員叫它清蒸桂花魚?” “對啊,桂花魚,清蒸的,營養(yǎng)健康,色香味俱全?!鼻鍝P(yáng)麻利地回應(yīng)著。
袁帥拿起菜單看了一眼,嘴里邊嘟囔邊若有所悟地點(diǎn)頭著:“在我老家這個(gè)菜叫「清蒸鱖魚」?!?/p>
清揚(yáng)瞥了他一眼,作搖頭嘆息狀伸手去夾魚,開心地吃了起來?!靶」韰柡Π?,竟然點(diǎn)了我最喜歡的魚,這頓飯我請了哈~” 袁帥這次快速從他的菜品命名的思緒中跳脫出來。
吃完飯回來,袁帥喊上清揚(yáng)去看看隔壁石彪的團(tuán)隊(duì)在做的Code Review,見到大屏幕上的代碼:
“小豹,這個(gè)FlyLine是指飛行路線嗎?” 石彪小心翼翼地問。
“嗯,是這個(gè)意思!”
“你在IDE搜一下【Route】” 只見小豹比較嫻熟地用快捷鍵定位到一個(gè)Route類:
看到這個(gè)類,小豹快速在正開著的Google翻譯框里查到【Route:航線】。
“你打開Trello卡上的【航空術(shù)語】那張卡片上的有個(gè)航空術(shù)語鏈接。你打開頁面敲關(guān)鍵字【航線】進(jìn)行搜索。”石彪見小豹下意識撓著頭不知所措,就進(jìn)一步給了提示。
石彪10秒鐘的指尖操作后,大屏幕上將頁面展示出來:
“小豹?jiǎng)倎?,對這些航空行業(yè)術(shù)語不熟悉可以理解,我提前給大家分享一條我的經(jīng)驗(yàn):入行說行話,既然咱們在開發(fā)航空系統(tǒng),我們就要講航空行話。” 石彪刻意掃視了所有在場的小伙伴,繼續(xù)說道:“那怎么學(xué)會這些行話呢,翻譯工具是一個(gè)好助手,但有時(shí)它也會不靈,咱們團(tuán)隊(duì)一直有在維護(hù)了一套專門的術(shù)語,上周我剛把它開發(fā)成Web版部署在內(nèi)網(wǎng)服務(wù)器上了,咱們隨時(shí)可以在上面檢索術(shù)語哈?!?/p>
石彪說完,小豹立刻示意結(jié)對的小伙伴在卡片上記下了一個(gè)Action:[ 更換Flight類中引用Flyline --> Route ]。
小結(jié)
如果說「域冗余」、「碼尾禪」、「層錯(cuò)綜」這些是代碼在溝通表達(dá)時(shí)的 偏形,那「偽修飾」、「行外話」這些就屬 走神 了,而這些貌合神離的假話更容易蠱惑人心。
寫代碼本沒什么大事,努力打磨這五點(diǎn),時(shí)刻審視代碼,時(shí)刻自省,軟件的可理解性可提升至少90%,沒錯(cuò)就是很高。