电竞比分网-中国电竞赛事及体育赛事平台

分享

關(guān)于爛代碼的那些事(下)

 flyhui321 2016-01-15





假設(shè)你已經(jīng)讀過(guò)爛代碼系列的前兩篇:了解了什么是爛代碼,什么是好代碼,但是還是不可避免的接觸到了爛代碼(就像之前說(shuō)的,幾乎沒(méi)有程序員可以完全避免寫(xiě)出爛代碼!)接下來(lái)的問(wèn)題便是:如何應(yīng)對(duì)這些身邊的爛代碼。


1.改善可維護(hù)性


改善代碼質(zhì)量是項(xiàng)大工程,要開(kāi)始這項(xiàng)工程,從可維護(hù)性入手往往是一個(gè)好的開(kāi)始,但也僅僅只是開(kāi)始而已。


1.1.重構(gòu)的悖論


很多人把重構(gòu)當(dāng)做一種一次性運(yùn)動(dòng),代碼實(shí)在是爛的沒(méi)法改了,或者沒(méi)什么新的需求了,就召集一幫人專(zhuān)門(mén)拿出來(lái)一段時(shí)間做重構(gòu)。這在傳統(tǒng)企業(yè)開(kāi)發(fā)中多少能生效,但是對(duì)于互聯(lián)網(wǎng)開(kāi)發(fā)來(lái)說(shuō)卻很難適應(yīng),原因有兩個(gè):


  • 互聯(lián)網(wǎng)開(kāi)發(fā)講究快速迭代,如果要做大型重構(gòu),往往需要暫停需求開(kāi)發(fā),這個(gè)基本上很難實(shí)現(xiàn)。

  • 對(duì)于沒(méi)有什么新需求的項(xiàng)目,往往意味著項(xiàng)目本身已經(jīng)過(guò)了發(fā)展期,即使做了重構(gòu)也帶來(lái)不了什么收益。


這就形成了一個(gè)悖論:一方面那些變更頻繁的系統(tǒng)更需要重構(gòu);另一方面重構(gòu)又會(huì)耽誤開(kāi)發(fā)進(jìn)度,影響變更效率。


面對(duì)這種矛盾,一種方式是放棄重構(gòu),讓代碼質(zhì)量自然下降,直到工程的生命周期結(jié)束,選擇放棄或者重來(lái)。在某些場(chǎng)景下這種方式確實(shí)是有效的,但是我并不喜歡:比起讓工程師不得不把每天的精力都浪費(fèi)在毫無(wú)意義的事情上,為什么不做些更有意義的事呢?


1.2.重構(gòu)step by step


1.2.1.開(kāi)始之前


開(kāi)始改善代碼的第一步是把IDE的重構(gòu)快捷鍵設(shè)到一個(gè)順手的鍵位上,這一步非常重要:決定重構(gòu)成敗的往往不是你的新設(shè)計(jì)有多么牛逼,而是重構(gòu)本身會(huì)占用多少時(shí)間。


比如對(duì)于IDEA來(lái)說(shuō),我會(huì)把重構(gòu)菜單設(shè)為快捷鍵:



這樣在我想去重構(gòu)的時(shí)候就可以隨手打開(kāi)菜單,而不是用鼠標(biāo)慢慢去點(diǎn),快捷鍵每次只能為重構(gòu)節(jié)省幾秒鐘時(shí)間,但是卻能明顯減少工程師重構(gòu)時(shí)的心理負(fù)擔(dān),后面會(huì)提到,小規(guī)模的重構(gòu)應(yīng)該跟敲代碼一樣屬于日常開(kāi)發(fā)的一部分。


我把重構(gòu)分為三類(lèi):模塊內(nèi)部的重構(gòu)、模塊級(jí)別的重構(gòu)、工程級(jí)別的重構(gòu)。分為這三類(lèi)并不是因?yàn)槲沂鞘裁捶诸?lèi)強(qiáng)迫癥,后面會(huì)看到對(duì)重構(gòu)的分類(lèi)對(duì)于重構(gòu)的意義。


1.2.2.隨時(shí)進(jìn)行模塊內(nèi)部的重構(gòu)


模塊內(nèi)部重構(gòu)的目的是把模塊內(nèi)部的邏輯梳理清楚,并且把一個(gè)巨大無(wú)比的函數(shù)拆分成可維護(hù)的小塊代碼。大部分IDE都提供了對(duì)這類(lèi)重構(gòu)的支持,類(lèi)似于:


  • 重命名變量

  • 重命名函數(shù)

  • 提取內(nèi)部函數(shù)

  • 提取內(nèi)部常量

  • 提取變量


這類(lèi)重構(gòu)的特點(diǎn)是修改基本集中在一個(gè)地方,對(duì)代碼邏輯的修改很少并且基本可控,IDE的重構(gòu)工具比較健壯,因而基本沒(méi)有什么風(fēng)險(xiǎn)。


以下例子演示了如何通過(guò)IDE把一個(gè)冗長(zhǎng)的函數(shù)做重構(gòu):



上圖的例子中,我們基本依靠IDE就把一個(gè)冗長(zhǎng)的函數(shù)分成了兩個(gè)子函數(shù),接下來(lái)就可以針對(duì)子函數(shù)中的一些爛代碼做進(jìn)一步的小規(guī)模重構(gòu),而兩個(gè)函數(shù)內(nèi)部的重構(gòu)也可以用同樣的方法。每一次小規(guī)模重構(gòu)的時(shí)間都不應(yīng)該超過(guò)60s,否則將會(huì)嚴(yán)重影響開(kāi)發(fā)的效率,進(jìn)而導(dǎo)致重構(gòu)被無(wú)盡的開(kāi)發(fā)需求淹沒(méi)。


在這個(gè)階段需要對(duì)現(xiàn)有的模塊補(bǔ)充一些單元測(cè)試,以保證重構(gòu)的正確。不過(guò)以我的經(jīng)驗(yàn)來(lái)看,一些簡(jiǎn)單的重構(gòu),例如修改局部變量名稱(chēng),或者提取變量之類(lèi)的重構(gòu),即使沒(méi)有測(cè)試也是基本可靠的,如果要在快速完成模塊內(nèi)部重構(gòu)和100%的單元測(cè)試覆蓋率中選一個(gè),我可能會(huì)選擇快速完成重構(gòu)。


而這類(lèi)重構(gòu)的收益主要是提高函數(shù)級(jí)別的可讀性,以及消除超大函數(shù),為未來(lái)進(jìn)一步做模塊級(jí)別的拆分打好基礎(chǔ)。


1.2.3.一次只做一個(gè)較模塊級(jí)別的的重構(gòu)


之后的重構(gòu)開(kāi)始牽扯到多個(gè)模塊,例如:


  • 刪除無(wú)用代碼

  • 移動(dòng)函數(shù)到其它類(lèi)

  • 提取函數(shù)到新類(lèi)

  • 修改函數(shù)邏輯


IDE往往對(duì)這類(lèi)重構(gòu)的支持有限,并且偶爾會(huì)出一些莫名其妙的問(wèn)題,(例如修改類(lèi)名時(shí)一不小心把配置文件里的常量字符串也給修改了)。


這類(lèi)重構(gòu)主要在于優(yōu)化代碼的設(shè)計(jì),剝離不相關(guān)的耦合代碼,在這類(lèi)重構(gòu)期間你需要?jiǎng)?chuàng)建大量新的類(lèi)和新的單元測(cè)試,而此時(shí)的單元測(cè)試則是必須的了。


為什么要?jiǎng)?chuàng)建單元測(cè)試?


  • 一方面,這類(lèi)重構(gòu)因?yàn)樯婕暗骄唧w代碼邏輯的修改,靠集成測(cè)試很難覆蓋所有情況,而單元測(cè)試可以驗(yàn)證修改的正確性。

  • 更重要的意義在于,寫(xiě)不出單元測(cè)試的代碼往往意味著糟糕的設(shè)計(jì):模塊依賴(lài)太多或者一個(gè)函數(shù)的職責(zé)太重,想象一下,想要執(zhí)行一個(gè)函數(shù)卻要模擬十幾個(gè)輸入對(duì)象,每個(gè)對(duì)象還要模擬自己依賴(lài)的對(duì)象……如果一個(gè)模塊無(wú)法被單獨(dú)測(cè)試,那么從設(shè)計(jì)的角度來(lái)考慮,無(wú)疑是不合格的。


還需要啰嗦一下,這里說(shuō)的單元測(cè)試只對(duì)一個(gè)模塊進(jìn)行測(cè)試,依賴(lài)多個(gè)模塊共同完成的測(cè)試并不包含在內(nèi)-例如在內(nèi)存里模擬了一個(gè)數(shù)據(jù)庫(kù),并在上層代碼中測(cè)試業(yè)務(wù)邏輯-這類(lèi)測(cè)試并不能改善你的設(shè)計(jì)。


在這個(gè)期間還會(huì)寫(xiě)一些過(guò)渡用的臨時(shí)邏輯,比如各種adapter、proxy或者wrapper,這些臨時(shí)邏輯的生存期可能會(huì)有幾個(gè)月到幾年,這些看起來(lái)沒(méi)什么必要的工作是為了控制重構(gòu)范圍,例如:


class Foo {

String foo() {

...

}

}


如果要把函數(shù)聲明改成:


class Foo {

boolean foo() {

...

}

}


那么最好通過(guò)加一個(gè)過(guò)渡模塊來(lái)實(shí)現(xiàn):


class FooAdaptor {

private Foo foo;

boolean foo() {

return foo.foo().isEmpty();

}

}


這樣做的好處是修改函數(shù)時(shí)不需要改動(dòng)所有調(diào)用方,爛代碼的特征之一就是模塊間的耦合比較高,往往一個(gè)函數(shù)有幾十處調(diào)用,牽一發(fā)而動(dòng)全身。而一旦開(kāi)始全面改造,往往就會(huì)把一次看起來(lái)很簡(jiǎn)單的重構(gòu)演變成幾周的大工程,這種大規(guī)模重構(gòu)往往是不可靠的。


每次模塊級(jí)別的重構(gòu)都需要精心設(shè)計(jì),提前劃分好哪些是需要修改的,哪些是需要用兼容邏輯做過(guò)渡的。但實(shí)際動(dòng)手修改的時(shí)間都不應(yīng)該超過(guò)一天,如果超過(guò)一天就意味著這次重構(gòu)改動(dòng)太多,需要控制一下修改節(jié)奏了。


1.2.4.工程級(jí)別的重構(gòu)不能和任何其他任務(wù)并行


不安全的重構(gòu)相對(duì)而言影響范圍比較大,比如:


  • 修改工程結(jié)構(gòu)

  • 修改多個(gè)模塊


我更建議這類(lèi)操作不要用IDE,如果使用IDE,也只使用最簡(jiǎn)單的“移動(dòng)”操作。這類(lèi)重構(gòu)單元測(cè)試已經(jīng)完全沒(méi)有作用,需要集成測(cè)試的覆蓋。不過(guò)也不必緊張,如果只做“移動(dòng)”的話,大部分情況下基本的冒煙測(cè)試就可以保證重構(gòu)的正確性。


這類(lèi)重構(gòu)的目的是根據(jù)代碼的層次或者類(lèi)型進(jìn)行拆分,切斷循環(huán)依賴(lài)和結(jié)構(gòu)上不合理的地方。如果不知道如何拆分,可以依照如下思路:


  1. 優(yōu)先按部署場(chǎng)景進(jìn)行拆分,比如一部分代碼是公用的,一部分代碼是自己用的,可以考慮拆成兩個(gè)部分。換句話說(shuō),A服務(wù)的修改能不能影響B(tài)服務(wù)。

  2. 其次按照業(yè)務(wù)類(lèi)型拆分,兩個(gè)無(wú)關(guān)的功能可以拆分成兩個(gè)部分。換句話說(shuō),A功能的修改能不能影響B(tài)功能。

  3. 除此之外,盡量控制自己的代碼潔癖,不要把代碼切成一大堆豆腐塊,會(huì)給日后的維護(hù)工作帶來(lái)很多不必要的成本。

  4. 案可以提前review幾次,多參考一線工程師的意見(jiàn),避免實(shí)際動(dòng)手時(shí)才冒出新的問(wèn)題。


而這類(lèi)重構(gòu)絕對(duì)不能跟正常的需求開(kāi)發(fā)并行執(zhí)行:代碼沖突幾乎無(wú)法避免,并且會(huì)讓所有人崩潰。我的做法一般是在這類(lèi)重構(gòu)前先演練一次:把模塊按大致的想法拖來(lái)拖去,通過(guò)編譯器找到依賴(lài)問(wèn)題,在日常上線中把容易處理的依賴(lài)問(wèn)題解決掉;然后集中團(tuán)隊(duì)里的精英,通知所有人暫停開(kāi)發(fā),花最多2、3天時(shí)間把所有問(wèn)題集中突擊掉,新的需求都在新代碼的基礎(chǔ)上進(jìn)行開(kāi)發(fā)。


如果歷史包袱實(shí)在太重,可以把這類(lèi)重構(gòu)也拆成幾次做:先大體拆分成幾塊,再分別拆分。無(wú)論如何,這類(lèi)重構(gòu)務(wù)必控制好變更范圍,一次嚴(yán)重的合并沖突有可能讓團(tuán)隊(duì)中的所有人幾個(gè)周緩不過(guò)勁來(lái)。


1.3.重構(gòu)的周期


典型的重構(gòu)周期類(lèi)似下面的過(guò)程:


  1. 在正常需求開(kāi)發(fā)的同時(shí)進(jìn)行模塊內(nèi)部的重構(gòu),同時(shí)理解工程原有代碼。

  2. 在需求間隙進(jìn)行模塊級(jí)別的重構(gòu),把大模塊拆分為多個(gè)小模塊,增加腳手架類(lèi),補(bǔ)充單元測(cè)試,等等。

  3. (如果有必要,比如工程過(guò)于巨大導(dǎo)致經(jīng)常出現(xiàn)相互影響問(wèn)題)進(jìn)行一次工程級(jí)別的拆分,期間需要暫停所有開(kāi)發(fā)工作,并且這次重構(gòu)除了移動(dòng)模塊和移動(dòng)模塊帶來(lái)的修改之外不做任何其他變更。

  4. 重復(fù)1、2步驟


1.3.1.一些重構(gòu)的tips


  1. 只重構(gòu)經(jīng)常修改的部分,如果代碼一兩年都沒(méi)有修改過(guò),那么說(shuō)明改動(dòng)的收益很小,重構(gòu)能改善的只是可維護(hù)性,重構(gòu)不維護(hù)的代碼不會(huì)帶來(lái)收益。

  2. 抑制住自己想要多改一點(diǎn)的沖動(dòng),一次失敗的重構(gòu)對(duì)代碼質(zhì)量改進(jìn)的影響可能是毀滅性的。

  3. 重構(gòu)需要不斷的練習(xí),相比于寫(xiě)代碼來(lái)說(shuō),重構(gòu)或許更難一些。

  4. 重構(gòu)可能需要很長(zhǎng)時(shí)間,有可能甚至?xí)_(dá)到幾年的程度(我之前用斷斷續(xù)續(xù)兩年多的時(shí)間重構(gòu)了一個(gè)項(xiàng)目),主要取決于團(tuán)隊(duì)對(duì)于風(fēng)險(xiǎn)的容忍程度。

  5. 刪除無(wú)用代碼是提高代碼可維護(hù)性最有效的方式,切記,切記。

  6. 單元測(cè)試是重構(gòu)的基礎(chǔ),如果對(duì)單元測(cè)試的概念還不是很清晰,可以參考《使用Spock框架進(jìn)行單元測(cè)試》。


2.改善性能與健壯性


2.1.改善性能的80%


性能這個(gè)話題越來(lái)越多的被人提起,隨便收到一份簡(jiǎn)歷不寫(xiě)上點(diǎn)什么熟悉高并發(fā)、做過(guò)性能優(yōu)化之類(lèi)的似乎都不好意思跟人打招呼。


說(shuō)個(gè)真事,幾年前在我做某公司的ERP項(xiàng)目,里面有個(gè)功能是生成一個(gè)報(bào)表。而使用我們系統(tǒng)的公司里有一個(gè)人,他每天要在下班前點(diǎn)一下報(bào)表,導(dǎo)出到excel,再發(fā)一封郵件出去。


問(wèn)題是,那個(gè)報(bào)表每次都要2,3分鐘才能生成。


我當(dāng)時(shí)正年輕氣盛,看到有個(gè)兩分鐘才能生成的報(bào)表一下就來(lái)了興趣,翻出了那段不知道誰(shuí)寫(xiě)的代碼,發(fā)現(xiàn)里面用了3層循環(huán),每次都會(huì)去數(shù)據(jù)庫(kù)查一次數(shù)據(jù),再把一堆數(shù)據(jù)拼起來(lái),一股腦塞進(jìn)一個(gè)tableview里。


面對(duì)這種代碼,我還能做什么呢?


  • 我立刻把那個(gè)三層循環(huán)干掉了,通過(guò)一個(gè)存儲(chǔ)過(guò)程直接輸出數(shù)據(jù)。

  • sql數(shù)據(jù)計(jì)算的邏輯也被我精簡(jiǎn)了,一些沒(méi)必要做的外聯(lián)操作被我干掉了。

  • 我還發(fā)現(xiàn)很多ctrl v生成的無(wú)用的控件(那時(shí)還是用的delphi),那些控件密密麻麻的貼在顯示界面上,只是被前面的大table擋住了,我當(dāng)然也把這些玩意都刪掉了;

  • 打開(kāi)界面的時(shí)候還做了一些雜七雜八的工作(比如去數(shù)據(jù)庫(kù)里更新點(diǎn)擊數(shù)之類(lèi)的),我把這些放到了異步任務(wù)里。

  • 后面我又覺(jué)得沒(méi)必要每次打開(kāi)界面都要加載所有數(shù)據(jù)(那個(gè)tableview有幾千行,幾百列!),于是我hack了默認(rèn)的tableview,每次打開(kāi)的時(shí)候先計(jì)算當(dāng)前實(shí)際顯示了多少內(nèi)容,把參數(shù)發(fā)給存儲(chǔ)過(guò)程,初始化只加載這些數(shù)據(jù),剩下的再通過(guò)線程異步加載。


做了這些之后,界面只需要不到1s就能展示出來(lái)了,不過(guò)我要說(shuō)的不是這個(gè)。


后來(lái)我去客戶公司給那個(gè)操作員演示新的模塊的時(shí)候,點(diǎn)一下,刷,數(shù)據(jù)出來(lái)了。那個(gè)人很驚恐的看著我,然后問(wèn)我,是不是數(shù)據(jù)不準(zhǔn)了。


再后來(lái),我又加了一個(gè)功能,那個(gè)模塊每次打開(kāi)之后都會(huì)顯示一個(gè)進(jìn)度條,上面的標(biāo)題是“正在校驗(yàn)數(shù)據(jù)……”,進(jìn)度條走完大概要1分鐘左右,我跟那人說(shuō)校驗(yàn)數(shù)據(jù)計(jì)算量很大,會(huì)比較慢。當(dāng)然,實(shí)際上那60秒里程序毛事都沒(méi)做,只是在一點(diǎn)點(diǎn)的更新那個(gè)進(jìn)度條(我還做了個(gè)彩蛋,在讀進(jìn)度的時(shí)候按上上下下左右左右BABA的話就可以加速10倍讀條…)。客戶很開(kāi)心,說(shuō)感覺(jué)數(shù)據(jù)準(zhǔn)確多了,當(dāng)然,他沒(méi)發(fā)現(xiàn)彩蛋。


我寫(xiě)了這么多,是想讓你明白一個(gè)事實(shí):大部分程序?qū)π阅懿⒉幻舾?。而少?shù)對(duì)性能敏感的程序里,一大半可以靠調(diào)節(jié)參數(shù)解決性能問(wèn)題;最后那一小撮需要修改代碼優(yōu)化性能的程序里,性?xún)r(jià)比高的工作又是少數(shù)。


什么是性?xún)r(jià)比?回到剛才的例子里,我做了那么多事,每件事的收益是多少?


  • 把三層循環(huán)sql改成了存儲(chǔ)過(guò)程,大概讓我花了一天時(shí)間,讓加載時(shí)間從3分鐘變成了2秒,模塊加載變成了”唰“的一下。

  • 后面的一坨事情大概花了我一周多時(shí)間,尤其是hack那個(gè)tableview,讓我連周末都搭進(jìn)去了。而所有的優(yōu)化加起來(lái),大概優(yōu)化了1秒左右,這個(gè)數(shù)據(jù)是通過(guò)日志查到的:即使是我自己,打開(kāi)模塊也沒(méi)感覺(jué)出有什么明顯區(qū)別。


我現(xiàn)在遇到的很多面試者說(shuō)程序優(yōu)化時(shí)總是喜歡說(shuō)一些玄乎的東西:調(diào)用棧、尾遞歸、內(nèi)聯(lián)函數(shù)、GC調(diào)優(yōu)……但是當(dāng)我問(wèn)他們:把一個(gè)普通函數(shù)改成內(nèi)聯(lián)函數(shù)是把原來(lái)運(yùn)行速度是多少的程序優(yōu)化成多少了,卻很少有人答出來(lái);或者是扭扭捏捏的說(shuō),應(yīng)該很多,因?yàn)檫@個(gè)函數(shù)會(huì)被調(diào)用很多遍。我再問(wèn)會(huì)被調(diào)用多少遍,每遍是多長(zhǎng)時(shí)間,就答不上來(lái)了。


所以關(guān)于性能優(yōu)化,我有兩個(gè)觀點(diǎn):


  1. 優(yōu)化主要部分,把一次網(wǎng)絡(luò)IO改為內(nèi)存計(jì)算帶來(lái)的收益遠(yuǎn)大于捯飭編譯器優(yōu)化之類(lèi)的東西。這部分內(nèi)容可以參考Numbers you should know;或者自己寫(xiě)一個(gè)for循環(huán),做一個(gè)無(wú)限i 的程序,看看一秒鐘i能累加多少次,感受一下cpu和內(nèi)存的性能。

  2. 性能優(yōu)化之后要有量化數(shù)據(jù),明確的說(shuō)出優(yōu)化后哪個(gè)指標(biāo)提升了多少。如果有人因?yàn)椤碧嵘阅堋爸?lèi)的理由寫(xiě)了一堆讓人無(wú)法理解的代碼,請(qǐng)務(wù)必讓他給出性能數(shù)據(jù):這很有可能是一坨沒(méi)有什么收益的爛代碼。


至于具體的優(yōu)化措施,無(wú)外乎幾類(lèi):


  1. 讓計(jì)算靠近存儲(chǔ)

  2. 優(yōu)化算法的時(shí)間復(fù)雜度

  3. 減少無(wú)用的操作

  4. 并行計(jì)算


關(guān)于性能優(yōu)化的話題還可以講很多內(nèi)容,不過(guò)對(duì)于這篇文章來(lái)說(shuō)有點(diǎn)跑題,這里就不再詳細(xì)展開(kāi)了。


2.2.決定健壯性的20%


前一陣聽(tīng)一個(gè)技術(shù)分享,說(shuō)是他們?cè)诰幊痰臅r(shí)候要考慮太陽(yáng)黑子對(duì)cpu計(jì)算的影響,或者是農(nóng)民伯伯的豬把基站拱塌了之類(lèi)的特殊場(chǎng)景。如果要優(yōu)化程序的健壯性,那么有時(shí)候就不得不去考慮這些極端情況對(duì)程序的影響。


大部分的人應(yīng)該不用考慮太陽(yáng)黑子之類(lèi)的高深的問(wèn)題,但是我們需要考慮一些常見(jiàn)的特殊場(chǎng)景,大部分程序員的代碼對(duì)于一些特殊場(chǎng)景都會(huì)有或多或少考慮不周全的地方,例如:


  • 用戶輸入

  • 并發(fā)

  • 網(wǎng)絡(luò)IO


常規(guī)的方法確實(shí)能夠發(fā)現(xiàn)代碼中的一些bug,但是到了復(fù)雜的生產(chǎn)環(huán)境中時(shí),總會(huì)出現(xiàn)一些完全沒(méi)有想到的問(wèn)題。雖然我也想了很久,遺憾的是,對(duì)于健壯性來(lái)說(shuō),我并沒(méi)有找到什么立竿見(jiàn)影的解決方案,因此,我只能謹(jǐn)慎的提出一點(diǎn)點(diǎn)建議:


  • 更多的測(cè)試測(cè)試的目的是保證代碼質(zhì)量,但測(cè)試并不等于質(zhì)量,你做覆蓋80%場(chǎng)景的測(cè)試,在20%測(cè)試不到的地方還是有可能出問(wèn)題。關(guān)于測(cè)試又是一個(gè)巨大的話題,這里就先不展開(kāi)了。

  • 謹(jǐn)慎發(fā)明輪子。例如UI庫(kù)、并發(fā)庫(kù)、IO client等等,在能滿足要求的情況下盡量采用成熟的解決方案,所謂的“成熟”也就意味著經(jīng)歷了更多實(shí)際使用環(huán)境下的測(cè)試,大部分情況下這種測(cè)試的效果是更好的。


3.改善生存環(huán)境


看了上面的那么多東西之后,你可以想一下這么個(gè)場(chǎng)景:


在你做了很多事情之后,代碼質(zhì)量似乎有了質(zhì)的飛躍。正當(dāng)你以為終于可以擺脫天天踩屎的日子了的時(shí)候,某次不小心瞥見(jiàn)某個(gè)類(lèi)又長(zhǎng)到幾千行了。


你憤怒的翻看提交日志,想找出罪魁禍?zhǔn)资钦l(shuí),結(jié)果卻發(fā)現(xiàn)每天都會(huì)有人往文件里提交那么十幾二十行代碼,每次的改動(dòng)看起來(lái)都沒(méi)什么問(wèn)題,但是日積月累,一年年過(guò)去,當(dāng)初花了九牛二虎之力重構(gòu)的工程又成了一坨爛代碼……


任何一個(gè)對(duì)代碼有追求的程序員都有可能遇到這種問(wèn)題,技術(shù)在更新,需求在變化,公司人員會(huì)流動(dòng),而代碼質(zhì)量總會(huì)在不經(jīng)意間偷偷的變差……


想要改善代碼質(zhì)量,最后往往就會(huì)變成改善生存環(huán)境。


3.1.1.統(tǒng)一環(huán)境


團(tuán)隊(duì)需要一套統(tǒng)一的編碼規(guī)范、統(tǒng)一的語(yǔ)言版本、統(tǒng)一的編輯器配置、統(tǒng)一的文件編碼,如果有條件最好能使用統(tǒng)一的操作系統(tǒng),這能避免很多無(wú)意義的工作。


就好像最近渣浪給開(kāi)發(fā)全部換成了統(tǒng)一的macbook,一夜之間以前的很多問(wèn)題都變得不是問(wèn)題了:字符集、換行符、IDE之類(lèi)的問(wèn)題只要一個(gè)配置文件就解決了,不再有各種稀奇古怪的代碼沖突或者不兼容的問(wèn)題,也不會(huì)有人突然提交上來(lái)一些編碼格式稀奇古怪的文件了。


3.1.2.代碼倉(cāng)庫(kù)


代碼倉(cāng)庫(kù)基本上已經(jīng)是每個(gè)公司的標(biāo)配,而現(xiàn)在的代碼倉(cāng)庫(kù)除了儲(chǔ)存代碼,還可以承擔(dān)一些團(tuán)隊(duì)溝通、代碼review甚至工作流程方面的任務(wù),如今這類(lèi)開(kāi)源的系統(tǒng)很多,像gitlab(github)、Phabricator這類(lèi)優(yōu)秀的工具都能讓代碼管理變得簡(jiǎn)單很多。我這里無(wú)意討論svn、git、hg還是什么其它的代碼管理工具更好,就算最近火熱的git在復(fù)雜性和集中化管理上也有一些問(wèn)題,其實(shí)我是比較期待能有替代git的工具產(chǎn)生的,扯遠(yuǎn)了。


代碼倉(cāng)庫(kù)的意義在于讓更多的人能夠獲得和修改代碼,從而提高代碼的生命周期,而代碼本身的生命周期足夠持久,對(duì)代碼質(zhì)量做的優(yōu)化才有意義。


3.1.3.持續(xù)反饋


大多數(shù)爛代碼就像癌癥一樣,當(dāng)爛代碼已經(jīng)產(chǎn)生了可以感覺(jué)到的影響時(shí),基本已經(jīng)是晚期,很難治好了。


因此提前發(fā)現(xiàn)代碼變爛的趨勢(shì)很重要,這類(lèi)工作可以依賴(lài)類(lèi)似于checkstyle,findbug之類(lèi)的靜態(tài)檢查工具,及時(shí)發(fā)現(xiàn)代碼質(zhì)量下滑的趨勢(shì),例如:


  1. 每天都在產(chǎn)生大量的新代碼

  2. 測(cè)試覆蓋率下降

  3. 靜態(tài)檢查的問(wèn)題增多


有了代碼倉(cāng)庫(kù)之后,就可以把這種工具與倉(cāng)庫(kù)的觸發(fā)機(jī)制結(jié)合起來(lái),每次提交的時(shí)候做覆蓋率、靜態(tài)代碼檢查等工作,jenkins sonarqube或者類(lèi)似的工具就可以完成基本的流程:伴隨著代碼提交進(jìn)行各種靜態(tài)檢查、運(yùn)行各種測(cè)試、生成報(bào)告并供人參考。


在實(shí)踐中會(huì)發(fā)現(xiàn),關(guān)于持續(xù)反饋的五花八門(mén)的工具很多,但是真正有用的往往只有那么一兩個(gè),大部分人并不會(huì)去在每次提交代碼之后再打開(kāi)一個(gè)網(wǎng)頁(yè)點(diǎn)擊“生成報(bào)告”,或者去登陸什么系統(tǒng)看一下測(cè)試的覆蓋率是不是變低了,因此一個(gè)一站式的系統(tǒng)大多數(shù)情況下會(huì)表現(xiàn)的更好。與其追求更多的功能,不如把有限的幾個(gè)功能整合起來(lái),例如我們把代碼管理、回歸測(cè)試、代碼檢查、和code review集成起來(lái),就是這個(gè)樣子:



當(dāng)然,關(guān)于持續(xù)集成還可以做的更多,篇幅所限,就不多說(shuō)了。


3.1.4.質(zhì)量文化


不同的團(tuán)隊(duì)文化會(huì)對(duì)技術(shù)產(chǎn)生微妙的影響,關(guān)于代碼質(zhì)量沒(méi)有什么共同的文化,每個(gè)公司都有自己的一套觀點(diǎn),并且似乎都能說(shuō)得通。


對(duì)于我自己來(lái)說(shuō),關(guān)于代碼質(zhì)量是這樣的觀點(diǎn):


  1. 爛代碼無(wú)法避免

  2. 爛代碼無(wú)法接受

  3. 爛代碼可以改進(jìn)

  4. 好的代碼能讓工作更開(kāi)心一些


如何讓大多數(shù)人認(rèn)同關(guān)于代碼質(zhì)量的觀點(diǎn)實(shí)際上是有一些難度的,大部分技術(shù)人員對(duì)代碼質(zhì)量的觀點(diǎn)是既不贊成、也不反對(duì)的中立態(tài)度,而代碼質(zhì)量就像是熵值一樣,放著不管總是會(huì)像更加混亂的方向演進(jìn),并且寫(xiě)爛代碼的成本實(shí)在是太低了,以至于一個(gè)實(shí)習(xí)生花上一個(gè)禮拜就可以毀了你花了半年精心設(shè)計(jì)的工程。


所以在提高代碼質(zhì)量時(shí),務(wù)必想辦法拉上團(tuán)隊(duì)里的其他人一起。雖然“引導(dǎo)團(tuán)隊(duì)提高代碼質(zhì)量”這件事情一開(kāi)始會(huì)很辛苦,但是一旦有了一些支持者,并且有了可以參考的模板之后,剩下的工作就簡(jiǎn)單多了。


這里推薦《布道之道:引領(lǐng)團(tuán)隊(duì)擁抱技術(shù)創(chuàng)新》這本書(shū),里面大部分的觀點(diǎn)對(duì)于代碼質(zhì)量也是可以借鑒的。僅靠喊口號(hào)很難讓其他人寫(xiě)出高質(zhì)量的代碼,讓團(tuán)隊(duì)中的其他人體會(huì)到高質(zhì)量代碼的收益,比喊口號(hào)更有說(shuō)服力。


4.最后再說(shuō)兩句


優(yōu)化代碼質(zhì)量是一件很有意思,也很有挑戰(zhàn)性的事情,而挑戰(zhàn)不光來(lái)自于代碼原本有多爛,要改進(jìn)的也并不只是代碼本身,還有工具、習(xí)慣、練習(xí)、開(kāi)發(fā)流程、甚至團(tuán)隊(duì)文化這些方方面面的事情。


寫(xiě)這一系列文章前前后后花了半年多時(shí)間,一直處在寫(xiě)一點(diǎn)刪一點(diǎn)的狀態(tài):我自身關(guān)于代碼質(zhì)量的想法和實(shí)踐也在經(jīng)歷著不斷變化。我更希望能寫(xiě)出一些能夠?qū)嵺`落地的東西,而不是喊喊口號(hào),忽悠忽悠“敏捷開(kāi)發(fā)”、“測(cè)試驅(qū)動(dòng)”之類(lèi)的幾個(gè)名詞就結(jié)束了。


但是在寫(xiě)文章的過(guò)程中就會(huì)慢慢發(fā)現(xiàn),很多問(wèn)題的改進(jìn)方法確實(shí)不是一兩篇文章可以說(shuō)明白的,問(wèn)題之間往往又相互關(guān)聯(lián),全都展開(kāi)說(shuō)甚至超出了一本書(shū)的信息量,所以這篇文章也只能刪去了很多內(nèi)容。


我參與過(guò)很多代碼質(zhì)量很好的項(xiàng)目,也參與過(guò)一些質(zhì)量很爛的項(xiàng)目,改進(jìn)了很多項(xiàng)目,也放棄了一些項(xiàng)目,從最初的單打獨(dú)斗自己改代碼,到后來(lái)帶領(lǐng)團(tuán)隊(duì)優(yōu)化工作流程,經(jīng)歷了很多。無(wú)論如何,關(guān)于爛代碼,我決定引用一下《布道之道》這本書(shū)里的一句話:


“‘更好’,其實(shí)不是一個(gè)目的地,而是一個(gè)方向…在當(dāng)前的位置和將來(lái)的目標(biāo)之間,可能有很多相當(dāng)不錯(cuò)的地方。你只需關(guān)注離開(kāi)現(xiàn)在的位置,而不要關(guān)心去向何方?!?/p>

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多