|
假設你已經讀過爛代碼系列的前兩篇:了解了什么是爛代碼,什么是好代碼,但是還是不可避免的接觸到了爛代碼(就像之前說的,幾乎沒有程序員可以完全避免寫出爛代碼?。┙酉聛淼膯栴}便是:如何應對這些身邊的爛代碼。 1.改善可維護性 改善代碼質量是項大工程,要開始這項工程,從可維護性入手往往是一個好的開始,但也僅僅只是開始而已。 1.1.重構的悖論 很多人把重構當做一種一次性運動,代碼實在是爛的沒法改了,或者沒什么新的需求了,就召集一幫人專門拿出來一段時間做重構。這在傳統(tǒng)企業(yè)開發(fā)中多少能生效,但是對于互聯(lián)網(wǎng)開發(fā)來說卻很難適應,原因有兩個:
這就形成了一個悖論:一方面那些變更頻繁的系統(tǒng)更需要重構;另一方面重構又會耽誤開發(fā)進度,影響變更效率。 面對這種矛盾,一種方式是放棄重構,讓代碼質量自然下降,直到工程的生命周期結束,選擇放棄或者重來。在某些場景下這種方式確實是有效的,但是我并不喜歡:比起讓工程師不得不把每天的精力都浪費在毫無意義的事情上,為什么不做些更有意義的事呢? 1.2.重構step by step 1.2.1.開始之前 開始改善代碼的第一步是把IDE的重構快捷鍵設到一個順手的鍵位上,這一步非常重要:決定重構成敗的往往不是你的新設計有多么牛逼,而是重構本身會占用多少時間。 比如對于IDEA來說,我會把重構菜單設為快捷鍵: 這樣在我想去重構的時候就可以隨手打開菜單,而不是用鼠標慢慢去點,快捷鍵每次只能為重構節(jié)省幾秒鐘時間,但是卻能明顯減少工程師重構時的心理負擔,后面會提到,小規(guī)模的重構應該跟敲代碼一樣屬于日常開發(fā)的一部分。 我把重構分為三類:模塊內部的重構、模塊級別的重構、工程級別的重構。分為這三類并不是因為我是什么分類強迫癥,后面會看到對重構的分類對于重構的意義。 1.2.2.隨時進行模塊內部的重構 模塊內部重構的目的是把模塊內部的邏輯梳理清楚,并且把一個巨大無比的函數(shù)拆分成可維護的小塊代碼。大部分IDE都提供了對這類重構的支持,類似于:
這類重構的特點是修改基本集中在一個地方,對代碼邏輯的修改很少并且基本可控,IDE的重構工具比較健壯,因而基本沒有什么風險。 以下例子演示了如何通過IDE把一個冗長的函數(shù)做重構: 上圖的例子中,我們基本依靠IDE就把一個冗長的函數(shù)分成了兩個子函數(shù),接下來就可以針對子函數(shù)中的一些爛代碼做進一步的小規(guī)模重構,而兩個函數(shù)內部的重構也可以用同樣的方法。每一次小規(guī)模重構的時間都不應該超過60s,否則將會嚴重影響開發(fā)的效率,進而導致重構被無盡的開發(fā)需求淹沒。 在這個階段需要對現(xiàn)有的模塊補充一些單元測試,以保證重構的正確。不過以我的經驗來看,一些簡單的重構,例如修改局部變量名稱,或者提取變量之類的重構,即使沒有測試也是基本可靠的,如果要在快速完成模塊內部重構和100%的單元測試覆蓋率中選一個,我可能會選擇快速完成重構。 而這類重構的收益主要是提高函數(shù)級別的可讀性,以及消除超大函數(shù),為未來進一步做模塊級別的拆分打好基礎。 1.2.3.一次只做一個較模塊級別的的重構 之后的重構開始牽扯到多個模塊,例如:
IDE往往對這類重構的支持有限,并且偶爾會出一些莫名其妙的問題,(例如修改類名時一不小心把配置文件里的常量字符串也給修改了)。 這類重構主要在于優(yōu)化代碼的設計,剝離不相關的耦合代碼,在這類重構期間你需要創(chuàng)建大量新的類和新的單元測試,而此時的單元測試則是必須的了。
在這個期間還會寫一些過渡用的臨時邏輯,比如各種adapter、proxy或者wrapper,這些臨時邏輯的生存期可能會有幾個月到幾年,這些看起來沒什么必要的工作是為了控制重構范圍,例如:
如果要把函數(shù)聲明改成:
那么最好通過加一個過渡模塊來實現(xiàn):
這樣做的好處是修改函數(shù)時不需要改動所有調用方,爛代碼的特征之一就是模塊間的耦合比較高,往往一個函數(shù)有幾十處調用,牽一發(fā)而動全身。而一旦開始全面改造,往往就會把一次看起來很簡單的重構演變成幾周的大工程,這種大規(guī)模重構往往是不可靠的。 每次模塊級別的重構都需要精心設計,提前劃分好哪些是需要修改的,哪些是需要用兼容邏輯做過渡的。但實際動手修改的時間都不應該超過一天,如果超過一天就意味著這次重構改動太多,需要控制一下修改節(jié)奏了。 1.2.4.工程級別的重構不能和任何其他任務并行 不安全的重構相對而言影響范圍比較大,比如:
我更建議這類操作不要用IDE,如果使用IDE,也只使用最簡單的“移動”操作。這類重構單元測試已經完全沒有作用,需要集成測試的覆蓋。不過也不必緊張,如果只做“移動”的話,大部分情況下基本的冒煙測試就可以保證重構的正確性。 這類重構的目的是根據(jù)代碼的層次或者類型進行拆分,切斷循環(huán)依賴和結構上不合理的地方。如果不知道如何拆分,可以依照如下思路:
而這類重構絕對不能跟正常的需求開發(fā)并行執(zhí)行:代碼沖突幾乎無法避免,并且會讓所有人崩潰。我的做法一般是在這類重構前先演練一次:把模塊按大致的想法拖來拖去,通過編譯器找到依賴問題,在日常上線中把容易處理的依賴問題解決掉;然后集中團隊里的精英,通知所有人暫停開發(fā),花最多2、3天時間把所有問題集中突擊掉,新的需求都在新代碼的基礎上進行開發(fā)。 如果歷史包袱實在太重,可以把這類重構也拆成幾次做:先大體拆分成幾塊,再分別拆分。無論如何,這類重構務必控制好變更范圍,一次嚴重的合并沖突有可能讓團隊中的所有人幾個周緩不過勁來。 1.3.重構的周期 典型的重構周期類似下面的過程:
1.3.1.一些重構的tips
2.改善性能與健壯性 2.1.改善性能的80% 性能這個話題越來越多的被人提起,隨便收到一份簡歷不寫上點什么熟悉高并發(fā)、做過性能優(yōu)化之類的似乎都不好意思跟人打招呼。 說個真事,幾年前在我做某公司的ERP項目,里面有個功能是生成一個報表。而使用我們系統(tǒng)的公司里有一個人,他每天要在下班前點一下報表,導出到excel,再發(fā)一封郵件出去。 問題是,那個報表每次都要2,3分鐘才能生成。 我當時正年輕氣盛,看到有個兩分鐘才能生成的報表一下就來了興趣,翻出了那段不知道誰寫的代碼,發(fā)現(xiàn)里面用了3層循環(huán),每次都會去數(shù)據(jù)庫查一次數(shù)據(jù),再把一堆數(shù)據(jù)拼起來,一股腦塞進一個tableview里。 面對這種代碼,我還能做什么呢?
做了這些之后,界面只需要不到1s就能展示出來了,不過我要說的不是這個。 后來我去客戶公司給那個操作員演示新的模塊的時候,點一下,刷,數(shù)據(jù)出來了。那個人很驚恐的看著我,然后問我,是不是數(shù)據(jù)不準了。 再后來,我又加了一個功能,那個模塊每次打開之后都會顯示一個進度條,上面的標題是“正在校驗數(shù)據(jù)……”,進度條走完大概要1分鐘左右,我跟那人說校驗數(shù)據(jù)計算量很大,會比較慢。當然,實際上那60秒里程序毛事都沒做,只是在一點點的更新那個進度條(我還做了個彩蛋,在讀進度的時候按上上下下左右左右BABA的話就可以加速10倍讀條…)??蛻艉荛_心,說感覺數(shù)據(jù)準確多了,當然,他沒發(fā)現(xiàn)彩蛋。 我寫了這么多,是想讓你明白一個事實:大部分程序對性能并不敏感。而少數(shù)對性能敏感的程序里,一大半可以靠調節(jié)參數(shù)解決性能問題;最后那一小撮需要修改代碼優(yōu)化性能的程序里,性價比高的工作又是少數(shù)。 什么是性價比?回到剛才的例子里,我做了那么多事,每件事的收益是多少?
我現(xiàn)在遇到的很多面試者說程序優(yōu)化時總是喜歡說一些玄乎的東西:調用棧、尾遞歸、內聯(lián)函數(shù)、GC調優(yōu)……但是當我問他們:把一個普通函數(shù)改成內聯(lián)函數(shù)是把原來運行速度是多少的程序優(yōu)化成多少了,卻很少有人答出來;或者是扭扭捏捏的說,應該很多,因為這個函數(shù)會被調用很多遍。我再問會被調用多少遍,每遍是多長時間,就答不上來了。 所以關于性能優(yōu)化,我有兩個觀點:
至于具體的優(yōu)化措施,無外乎幾類:
關于性能優(yōu)化的話題還可以講很多內容,不過對于這篇文章來說有點跑題,這里就不再詳細展開了。 2.2.決定健壯性的20% 前一陣聽一個技術分享,說是他們在編程的時候要考慮太陽黑子對cpu計算的影響,或者是農民伯伯的豬把基站拱塌了之類的特殊場景。如果要優(yōu)化程序的健壯性,那么有時候就不得不去考慮這些極端情況對程序的影響。 大部分的人應該不用考慮太陽黑子之類的高深的問題,但是我們需要考慮一些常見的特殊場景,大部分程序員的代碼對于一些特殊場景都會有或多或少考慮不周全的地方,例如:
常規(guī)的方法確實能夠發(fā)現(xiàn)代碼中的一些bug,但是到了復雜的生產環(huán)境中時,總會出現(xiàn)一些完全沒有想到的問題。雖然我也想了很久,遺憾的是,對于健壯性來說,我并沒有找到什么立竿見影的解決方案,因此,我只能謹慎的提出一點點建議:
3.改善生存環(huán)境 看了上面的那么多東西之后,你可以想一下這么個場景:
任何一個對代碼有追求的程序員都有可能遇到這種問題,技術在更新,需求在變化,公司人員會流動,而代碼質量總會在不經意間偷偷的變差…… 想要改善代碼質量,最后往往就會變成改善生存環(huán)境。 3.1.1.統(tǒng)一環(huán)境 團隊需要一套統(tǒng)一的編碼規(guī)范、統(tǒng)一的語言版本、統(tǒng)一的編輯器配置、統(tǒng)一的文件編碼,如果有條件最好能使用統(tǒng)一的操作系統(tǒng),這能避免很多無意義的工作。 就好像最近渣浪給開發(fā)全部換成了統(tǒng)一的macbook,一夜之間以前的很多問題都變得不是問題了:字符集、換行符、IDE之類的問題只要一個配置文件就解決了,不再有各種稀奇古怪的代碼沖突或者不兼容的問題,也不會有人突然提交上來一些編碼格式稀奇古怪的文件了。 3.1.2.代碼倉庫 代碼倉庫基本上已經是每個公司的標配,而現(xiàn)在的代碼倉庫除了儲存代碼,還可以承擔一些團隊溝通、代碼review甚至工作流程方面的任務,如今這類開源的系統(tǒng)很多,像gitlab(github)、Phabricator這類優(yōu)秀的工具都能讓代碼管理變得簡單很多。我這里無意討論svn、git、hg還是什么其它的代碼管理工具更好,就算最近火熱的git在復雜性和集中化管理上也有一些問題,其實我是比較期待能有替代git的工具產生的,扯遠了。 代碼倉庫的意義在于讓更多的人能夠獲得和修改代碼,從而提高代碼的生命周期,而代碼本身的生命周期足夠持久,對代碼質量做的優(yōu)化才有意義。 3.1.3.持續(xù)反饋 大多數(shù)爛代碼就像癌癥一樣,當爛代碼已經產生了可以感覺到的影響時,基本已經是晚期,很難治好了。 因此提前發(fā)現(xiàn)代碼變爛的趨勢很重要,這類工作可以依賴類似于checkstyle,findbug之類的靜態(tài)檢查工具,及時發(fā)現(xiàn)代碼質量下滑的趨勢,例如:
有了代碼倉庫之后,就可以把這種工具與倉庫的觸發(fā)機制結合起來,每次提交的時候做覆蓋率、靜態(tài)代碼檢查等工作,jenkins sonarqube或者類似的工具就可以完成基本的流程:伴隨著代碼提交進行各種靜態(tài)檢查、運行各種測試、生成報告并供人參考。 在實踐中會發(fā)現(xiàn),關于持續(xù)反饋的五花八門的工具很多,但是真正有用的往往只有那么一兩個,大部分人并不會去在每次提交代碼之后再打開一個網(wǎng)頁點擊“生成報告”,或者去登陸什么系統(tǒng)看一下測試的覆蓋率是不是變低了,因此一個一站式的系統(tǒng)大多數(shù)情況下會表現(xiàn)的更好。與其追求更多的功能,不如把有限的幾個功能整合起來,例如我們把代碼管理、回歸測試、代碼檢查、和code review集成起來,就是這個樣子: 當然,關于持續(xù)集成還可以做的更多,篇幅所限,就不多說了。 3.1.4.質量文化 不同的團隊文化會對技術產生微妙的影響,關于代碼質量沒有什么共同的文化,每個公司都有自己的一套觀點,并且似乎都能說得通。 對于我自己來說,關于代碼質量是這樣的觀點:
如何讓大多數(shù)人認同關于代碼質量的觀點實際上是有一些難度的,大部分技術人員對代碼質量的觀點是既不贊成、也不反對的中立態(tài)度,而代碼質量就像是熵值一樣,放著不管總是會像更加混亂的方向演進,并且寫爛代碼的成本實在是太低了,以至于一個實習生花上一個禮拜就可以毀了你花了半年精心設計的工程。 所以在提高代碼質量時,務必想辦法拉上團隊里的其他人一起。雖然“引導團隊提高代碼質量”這件事情一開始會很辛苦,但是一旦有了一些支持者,并且有了可以參考的模板之后,剩下的工作就簡單多了。 這里推薦《布道之道:引領團隊擁抱技術創(chuàng)新》這本書,里面大部分的觀點對于代碼質量也是可以借鑒的。僅靠喊口號很難讓其他人寫出高質量的代碼,讓團隊中的其他人體會到高質量代碼的收益,比喊口號更有說服力。 4.最后再說兩句 優(yōu)化代碼質量是一件很有意思,也很有挑戰(zhàn)性的事情,而挑戰(zhàn)不光來自于代碼原本有多爛,要改進的也并不只是代碼本身,還有工具、習慣、練習、開發(fā)流程、甚至團隊文化這些方方面面的事情。 寫這一系列文章前前后后花了半年多時間,一直處在寫一點刪一點的狀態(tài):我自身關于代碼質量的想法和實踐也在經歷著不斷變化。我更希望能寫出一些能夠實踐落地的東西,而不是喊喊口號,忽悠忽悠“敏捷開發(fā)”、“測試驅動”之類的幾個名詞就結束了。 但是在寫文章的過程中就會慢慢發(fā)現(xiàn),很多問題的改進方法確實不是一兩篇文章可以說明白的,問題之間往往又相互關聯(lián),全都展開說甚至超出了一本書的信息量,所以這篇文章也只能刪去了很多內容。 我參與過很多代碼質量很好的項目,也參與過一些質量很爛的項目,改進了很多項目,也放棄了一些項目,從最初的單打獨斗自己改代碼,到后來帶領團隊優(yōu)化工作流程,經歷了很多。無論如何,關于爛代碼,我決定引用一下《布道之道》這本書里的一句話:
|
|
|