精讀《維護好一個復(fù)雜項目》

現(xiàn)在許多國內(nèi)互聯(lián)網(wǎng)公司的項目都持續(xù)了五年左右,美國老牌公司如 IBM 的項目甚至持續(xù)維護了十五年,然而這些項目卻有著截然不同的維護成本,有的公司項目運作幾年后維護成本依然與初創(chuàng)期不大,可以保持較為高效的迭代速度,但有的項目甚至改幾個文案都會導(dǎo)致線上事故,研發(fā)效率變得越來越慢。

根據(jù)筆者的經(jīng)驗,嘗試總結(jié)一些持續(xù)維護項目變得難以維護的原因,以及如何設(shè)計才能保持良好的可維護性。

精讀

心態(tài)

如果不真心對待自己的項目,其實是很難做到良好可維護性的,所以第一點就是需要一個良好的心態(tài)。

作為項目管理者,一個項目一旦交給一位同學(xué)開發(fā),那么就要完全信任這位同學(xué)的能力,因為實際上你已經(jīng)不可能實質(zhì)性的影響到開發(fā)細(xì)節(jié)了。有人可能覺得好的流程或者事后 CodeReview 能發(fā)現(xiàn)一些問題,但這永遠(yuǎn)是杯水車薪,比如下面這個例子:

小張接到任務(wù)研發(fā)透視表,要求這個透視表具有良好的開發(fā)體驗并做好單測。

那怎么樣做單測才算是有效的,如何同時保證開發(fā)體驗?zāi)??不同人會有不同的想法,也會有不同的結(jié)果。

有主人翁心態(tài)的小張

對于一個有一定經(jīng)驗,又對項目真正上心的小張來說,開發(fā)過程可能是這樣的。

首先上來先寫主要功能,比如考慮數(shù)據(jù)模型、繪圖技術(shù)方案后,決定采用圖形語法方式定義數(shù)據(jù)結(jié)構(gòu),在做了一系列高性能前置考慮后,快速做出來了一個原型,包含表格的渲染、操作、翻頁、凍結(jié)等等功能。

但隨著需求的深入,小張發(fā)現(xiàn)做到下鉆、排序時,不知道為何影響到了列凍結(jié)的功能,而代碼架構(gòu)其實沒什么大問題,抽象的也很好,主要就是一些細(xì)節(jié)的代碼調(diào)用漏掉了,只要補上就立馬打通了任督二脈,整套功能再度行云流水了起來。但不知道下次做樹狀展示結(jié)構(gòu)時會不會又把之前的功能影響了,這始終是個隱患,于是小張開始思考先把單測加上再繼續(xù)開發(fā)功能。

由于出問題的場景有很小部分是大量操作后偶然引發(fā)的,普通的函數(shù)式單測也無法保證覆蓋的全面,因此小張決定做一個單測錄制功能,他首先把對表格的所有操作 Action 化,讓一套 json 可以描述所有用戶操作,然后又在本地開發(fā)界面做了一個單測錄制功能,即在頁面上對表格功能拖拖拽拽時,就會實時生成這套用戶操作 json,再把當(dāng)時頁面結(jié)構(gòu)與內(nèi)部狀態(tài)記錄下來作為對比依據(jù),單測就還原這套 json 并與基準(zhǔn)狀態(tài)做對比就行了。

小張很快錄制了很多原子操作的單測,比如表格的各種空數(shù)據(jù)狀態(tài)、單行單列渲染、列凍結(jié)行凍結(jié);然后又把一些功能混合的場景結(jié)合起來,比如列凍結(jié)時排序,翻頁后進(jìn)行下鉆;最后又把一些隨機復(fù)雜的功能組合在一起,形成一些日常容易出問題的特殊單測 case,比如表格單頁后突然清空數(shù)據(jù),再強制凍結(jié)第二列,再灌入3列數(shù)據(jù)并對第2行做排序,再取消列凍結(jié)并翻到第4頁。以后每當(dāng)遇到一個邊界 case 時,小張都會把這個問題 case 記錄到單測,驗證確實運行失敗,再進(jìn)行修復(fù),直到包含這個單測在內(nèi)的所有單測都驗證通過后,才算開發(fā)完成。

打工人小張

對于為了混口飯吃的小張來說,開發(fā)過程可能是這樣的。

首先上來寫主要功能,把各種表格功能做完后,也遇到了一樣的邊界 case 難題,此時小張本來想 case by case 修復(fù),但又想到 leader 要求他寫單測,覺得倒也不壞,就創(chuàng)建了單測目錄。

怎么寫單測呢?首先小張把遇到的問題修了,畢竟誰也不希望自己手里的 bug 太多,但至于錄到單測就太麻煩了,反正大家也不知道這個 case,修掉了就再也不會出來了吧,那就只把 leader 要求的幾個基本功能單測加上去,看下覆蓋率也達(dá)到硬性指標(biāo)就行了。

大團隊代碼總是容易走向混亂

假設(shè)你是 leader,你不知道自己團隊的小張到底是主人翁小張還是打工人小張,企圖通過 code review 來統(tǒng)一提升團隊的代碼質(zhì)量,實際上可行嗎?

如果不幸遇上了打工人小張,他在 code review 時展示的代碼結(jié)構(gòu)就不是能做整體單測的抽象,你只能看著單測文件硬提一些比如 “多加一些單測,多考慮一些情況” 的建議,實際上完全達(dá)不到主人翁小張做的效果。

這背后的原因是影響代碼質(zhì)量的因素太多,比如 Action 化,比如各種極端 case 的錄入,比如全流程的單測形式,這些對代碼來說都是質(zhì)變,但 code review 時看到的代碼就是不夠抽象,不夠 Action 化的,不可能把代碼推翻重寫一遍,只能在已有代碼基礎(chǔ)上提優(yōu)化建議,而到這個時候,神仙也沒法讓打工人小張的代碼優(yōu)化為主人翁小張的,除非推翻重寫。

這就是心態(tài)的影響力,能把項目做好的細(xì)節(jié)很多,而且細(xì)節(jié)之間還是環(huán)環(huán)相扣的,比如不把代碼 Action 化就不方便做整體單測,但如果開發(fā)者打一開始就沒想好好設(shè)計,code review 時又有多少人能想到這一點呢?想到了此時再提可能也為時太晚,一切都已成定局。

這些年筆者看過不少久經(jīng)歷史的代碼,因為大公司有大量的開發(fā)者維護同一個項目,每個人開發(fā)時的心態(tài)都各有不同,會發(fā)現(xiàn)總能看到那些模塊是用打工人心態(tài)做出來的,而你想徹底優(yōu)化就只能徹底重寫,但礙于項目體量太大時間上不允許,只能沿著打工人思路洋洋灑灑的繼續(xù)寫下去。

所以擁有一個良好,正面或者說積極的主人翁心態(tài)來寫代碼,一般來說都可以維護好復(fù)雜項目。

解耦

復(fù)雜項目的復(fù)雜指的是什么呢?是指功能多嗎?其實不然。

如果僅從功能多就判定這個項目復(fù)雜,那我們身處的社會才是最復(fù)雜的系統(tǒng),但社會中的每個玩家都沒有覺得吃穿住行很難,核心原因就在于了解我們用到的場景只需要少量的知識,而做出一個行動要得到正確的結(jié)果,也不會造成太大的影響。比如出門買菜,只要做個公交車到菜市場,掃一下碼就完成了交易,而不需要對背后的城市公交體系與菜市場背后的金融體系有任何深入的了解,你不需要理解公交車是哪兒來的,菜農(nóng)手里的菜是從哪兒收購的。

但代碼世界就很有趣了,在代碼世界買個菜可能會導(dǎo)致世界毀滅。這就導(dǎo)致每一個項目開發(fā)人員,哪怕是去買個菜,也要受過總統(tǒng)級訓(xùn)練,對各種國家級大事做出正確的預(yù)案,為什么會這樣呢?

因為代碼世界的邏輯是不同開發(fā)者碼出來的,在實現(xiàn)世界底層邏輯時可能就埋下了耦合的種子,導(dǎo)致你不知道為什么買菜會觸發(fā)那么嚴(yán)重的事情。舉個例子,改一個文案導(dǎo)致系統(tǒng)崩潰,原因可能是某處錯誤兜底邏輯用字面量判斷了這個文案,而你把文案改了,這個判斷就失效了。有的程序員挺難的,在這種項目環(huán)境下生存,每一步修改都要小心翼翼。

這個問題的解決辦法就是解耦,在這里我們不細(xì)說具體怎么解耦,因為每個場景的解耦方式都不同。我們只需要理解幾乎所有的業(yè)務(wù)邏輯都可以用解耦的方式做,就行了。只要你按照這樣的大思路去設(shè)計系統(tǒng),不論路徑是怎樣的,最終都能設(shè)計出一個漂亮的系統(tǒng)級方案。

比如做一個 BI 系統(tǒng),看上去里面有各種復(fù)雜的模塊可能會產(chǎn)生相互影響,比如數(shù)據(jù)處理、儀表盤搭建、大屏搭建、圖表、GIS 地圖等,在設(shè)計之初就要假裝其他模塊不存在,來考慮每個模塊必要的輸入是哪些。

比如布局,它僅僅用于對畫布進(jìn)行布局,為了保證布局系統(tǒng)是完全解耦的,必須讓項目支持在無布局的環(huán)境下運行。為了做到這一點,就必須讓布局真的 “只做布局”,而不存儲當(dāng)前畫布結(jié)構(gòu),這樣才不會因為布局系統(tǒng)被移除時,影響組件的聯(lián)動,因為組件聯(lián)動需要利用畫布結(jié)構(gòu) API。

圖層列表也可以和布局解耦,因為圖層列表只關(guān)心畫布的組件樹結(jié)構(gòu),而不關(guān)心布局是如何實現(xiàn)的,所以畫布的組件樹結(jié)構(gòu)就像生活中的金錢,大家都可以用它交易,而無需關(guān)心它流向了何方,被誰使用。

數(shù)據(jù)邏輯與畫布結(jié)構(gòu)無關(guān),只需要關(guān)心表達(dá)式以及用戶對維度度量的配置、聚合方式以及圖表本身的特性進(jìn)行查詢 sql 拼接即可,唯一用到的通用資源是當(dāng)前組件實例信息修改后,需要更新到畫布的組件樹上。

社會也是建立在這種底層認(rèn)同上,才能這么解耦的,所以在復(fù)雜項目中一定要有一個大家都認(rèn)可的底層概念,這個概念應(yīng)該盡可能通用化(想想金錢什么都能買,如果只能買蔬菜就麻煩了)、貫穿整個業(yè)務(wù)邏輯(金錢是現(xiàn)代社會任何交易都必須的媒介)。

許多項目被詬病難改,往往是沒有遵循這條邏輯,硬生生把可以不相關(guān)的概念耦合了。比如某個篩選器條件變化時,對某個組件做特殊操作,這個場景可以控制反轉(zhuǎn)為,這個組件在接收到某些篩選條件時,自己做特定的操作。因為對 BI 系統(tǒng)來說,篩選器的輸出要作為圖表繪圖的輸入,在這個底層框架下,就不要再開辟一條篩選器關(guān)心到具體圖表的邏輯了。

總結(jié)

維護好一個復(fù)雜項目很難,這次分享了兩個實踐中有用的方案,第一個抱有主人翁心態(tài)設(shè)計代碼,要在設(shè)計之初就做好考量,不要寄希望于對沒有好好設(shè)計的系統(tǒng)做縫縫補補。第二是深入理解為什么現(xiàn)代社會的運作巧妙之處,盡可能把代碼架構(gòu)組織一定程度映射到社會的運作機制上,目前來看,社會最適合代碼借鑒的思路就是解耦,再利用龐大的分工協(xié)作網(wǎng)絡(luò)完成單人無法完成的工作。

轉(zhuǎn)自:精讀《維護好一個復(fù)雜項目》·

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容