爛代碼,升維來看是什么問題導致的呢?

爛代碼是個有意思的話題,Martin Fowler 的著作《重構:改善既有代碼的設計》里面就專門提到了代碼異味(code smells),特別認同他說的:“代碼異味是一種表象,它通常對應于系統(tǒng)中更深層次的問題?!?br>
如果你的系統(tǒng)中到處都是爛代碼,就要思考深層次的原因了,需求太奇葩?進度太緊?水平不行?沒有設計?或者多個因素一起?
Martin給出的解決方案是重構,這絕對是很好的建議,只是執(zhí)行起來有難度!
- 我想重構代碼,但是我沒有工期怎么辦?
- 我好不容易重構完,會不會沒多久又老樣子了?
- 我自己沒有能力重構!
歸根結底不光是個技術問題,還涉及軟件工程和項目管理。
剛寫代碼的時候,都是爛代碼,后來見過好代碼,也開始學著提升代碼質(zhì)量,后來自己代碼質(zhì)量上來了發(fā)現(xiàn)別人寫爛代碼,等到帶團隊了就要去解決團隊中爛代碼。
理想態(tài)是團隊的代碼質(zhì)量不錯,即使有幾個剛畢業(yè)的新手程序員也不讓爛代碼混進去,這樣產(chǎn)出也高,一個功能實現(xiàn)速度是別的團隊的1.5倍到2倍甚至更快。
在解決團隊爛代碼問題上的嘗試。
最先嘗試的是代碼審查,所有更新的代碼都先在分支開發(fā),合并到主干前要代碼審查。順便說一下:對于直接在主干開發(fā),代碼合并后再審查那種效果是會大打折扣的,事后改起來很困難,而且優(yōu)先級會很低。
代碼審查嚴格執(zhí)行的話,效果是很明顯的。但也遇到很多問題:
1. 代碼命名、格式不符合規(guī)范,肉眼很難看的過來怎么辦?
2. PR(Pull Request)太大,要審查的代碼太多,看不過來怎么辦?
3. 急著要上線的功能,代碼質(zhì)量一般甚至很爛怎么辦?
4. 自動化測試覆蓋不全怎么辦?
5. 代碼能實現(xiàn)功能,執(zhí)行也沒問題,但是結構混亂或者沒有遵循最佳實踐,批準還是不批準?
6. 原本的項目就是個“屎山”代碼,新代碼只能修修補補怎么辦?
代碼審查的一部分工作要工具化自動化。
很多代碼審查,靠肉眼是查不過來的,需要靠工具配合:
- 源代碼管理工具不可少,可以清楚看到代碼改動的代碼審查工具不可少
- CI(持續(xù)集成)必不可少,每次提交代碼要借助CI運行一些自動化測試
- lint必不可少,借助lint檢查命名、格式等問題
- 自動化測試(單元測試和集成測試)不可少,代碼的更新不能破壞現(xiàn)有功能,新增了功能,相應的也要新增自動化測試代碼
PR不能太大,大PR要盡可能拆成小PR
越小的PR越好Review,反之太難,盡可能拆小
對于工期緊,急于合并的PR,質(zhì)量差的不要輕易合并,需要后續(xù)改進的要有Ticket跟蹤
生產(chǎn)環(huán)境的緊急補丁、deadline將近的新功能,這些PR很難說NO,只能先合并,但大部分時候就是合并了再無后續(xù),最終一點點污染了整個代碼庫。
所以總的經(jīng)驗就是:質(zhì)量差的,寧可推遲發(fā)布;質(zhì)量沒問題,但是測試不全的或者可以有優(yōu)化空間的,都要求創(chuàng)建一個或多個Ticket去跟蹤,并且這些Ticket的優(yōu)先級和其他正式功能的Ticket優(yōu)先級是一樣的,必須在后續(xù)的Sprint里面去盡快完成。
對于稍微復雜一點的功能,寫代碼之前要先做系統(tǒng)設計
在代碼審查時,最初經(jīng)常遇到的一個問題就是,一個功能安排下去,開發(fā)很快就實現(xiàn),結果你一看代碼,質(zhì)量也不算太差,但是沒有遵循好的實踐,單個代碼沒問題,合在一起就很難閱讀和維護。
要說PR不批準推翻重寫吧,人家都花了幾天甚至幾周時間在上面了,說要重寫肯定會很抵觸,要是小修改也意義不啊。這種情況很難處理。
后來就找到一個好的解決方案,就是先做設計,因為設計階段,只需要寫簡單的文檔,畫幾張結構圖,通過設計Review會議,一發(fā)現(xiàn)方向不對,馬上就可以調(diào)整,很容易就對齊思路,等到最終實現(xiàn)代碼,提交PR的時候,基本上和當初討論的設計不會有太大出入,也不會再出現(xiàn)前面說到的要推翻重寫的情況。
寫代碼之前先做設計絕對是磨刀不誤砍柴工的好事,寫設計文檔倒逼著開發(fā)人員在實現(xiàn)前先想清楚,設計Review也是一個很好的機會讓團隊其他成員學習怎么做設計,怎么遵循最佳實踐。
唯一的問題就是要求團隊里面有資深的開發(fā)人員,能在設計Review期間發(fā)現(xiàn)問題,指出問題,提出改進意見,否則就意義不大。
對于什么樣的功能需要做系統(tǒng)設計,我們團隊是用T恤尺碼來對應任務大小的,比如S、M、L、XL等,我們的要求是M及以上的都要求做系統(tǒng)設計,S的可以不做。
強制代碼審查后才能合并,以及強制先設計再寫代碼,很好的幫助了我的團隊控制代碼質(zhì)量。
但這還不夠,還經(jīng)常遇到的問題就是:一些舊的屎山項目怎么辦?技術債務怎么辦?
老項目的策略是這樣的:
1. 不值得維護的,隔離起來,保證它能運行就夠了,只打補丁,不增加新功能,也不會再更新
2. 需要長期維護的,需要經(jīng)常新增功能的,那么就要通過重構去改進完善。
老項目重構的常用策略是:
1. 先補自動化測試代碼,尤其是集成測試,這些自動化測試保證以后我做任何修改,都不會出大的問題
2. 逐個模塊替換,而不是一下子推翻重寫然后遷移。
比如要用NextJS重構一個重要的前端項目,不是先寫一個新項目,然后整個替換掉,而是先寫創(chuàng)建一個組件庫,讓這個組件庫在新舊項目中可以共用,每次新的組件寫好了,替換老項目中的相應模塊,在老項目中充分測試后,再應用到新項目,這樣等到老項目的模塊都替換的差不多了,新項目也已經(jīng)充分測試過了,遷移的風險就很小。
再說技術債務,借債不是壞事,但是債務是有利息的,最好的還債方式就是像房貸一樣,每個月定期還一部分,而不是攢最后一起還,那太難了。
首先會用任務管理系統(tǒng)跟蹤所有的技術債務,每個技術債務相關的任務都會創(chuàng)建成ticket,對于復雜的,會進一步拆分成小的ticket。
然后在每一個Sprint,我技術債務相關的任務在20%左右,這樣的任務和產(chǎn)品需求任務優(yōu)先級是一樣的。這樣基本上我的代碼庫的技術債務一直維持在一個很良性的水平。
有的團隊就是另一種情況,去年花了一個季度的時間去償還技術債務,因為到了實在非還不可的地步,那個季度基本上無法響應來自產(chǎn)品的需求。我個人是不太推薦這種方式的,對開發(fā)團隊和產(chǎn)品團隊都是巨大的壓力。
最近還探索出一個比較好的開發(fā)模式:
就是兩周的Sprint中,我們第一周只做產(chǎn)品的需求,然后第一周開發(fā)完成后部署到測試環(huán)境測試,第二周就是修復各種新功能的Bug,但是相應的bug修復工作量沒多少,所以我們在第二周就可以有時間去做純技術相關的任務,比如償還技術債務、測試新的技術棧、開發(fā)公共組件。因為持續(xù)有產(chǎn)品功能交付,所以產(chǎn)品經(jīng)理也接受了這種模式。
除了老項目的債務問題,新項目也有一個問題,就是架構設計出來了,代碼實現(xiàn)的時候,程序員的水平是參差不齊的,有新手有老手,就是是老手,還有各自的個人喜好在里面,這也給代碼質(zhì)量帶來很大挑戰(zhàn)。
所以如果是我自己設計的架構或者我?guī)У捻椖浚視诩軜嬙O計完成后,自己或者要求其他人基于架構設計,先實現(xiàn)一些基本的功能模塊,把基本的場景都覆蓋,在這些模塊實現(xiàn)的時候,形成一個好的開發(fā)實踐,比如前端項目,可以遵守Redux的狀態(tài)管理實踐。這個階段也一樣要有代碼審查,團隊里每個人都可以提出自己的意見反饋,根據(jù)反饋還可以對最佳實踐做出調(diào)整。
有了最佳實踐,其他人在實現(xiàn)時,就可以照葫蘆畫瓢,按照最佳實踐就可以寫出質(zhì)量不錯的代碼,對于不符合最佳實踐的代碼,在代碼審查階段就應該果斷拒絕。
簡單總結一下:
1.代碼審查很重要,而且一定要先審查再合并
2.系統(tǒng)設計也很重要,堅持先系統(tǒng)設計再對3.設計評審再實現(xiàn)代碼
4.代碼要有自動化測試覆蓋
5.技術債務要定期還
6.老項目重構可以一點點替換
7.新項目或已有項目要有最佳實踐供新手參考,并且大家一起遵守
所以爛代碼這個問題,升維來看本質(zhì)上需求管理問題,需求本身影響著實現(xiàn)質(zhì)量。缺乏專業(yè)的需求管理能力,盲目擴大需求范圍,力求多和快,忘記了好和省了。
