- 前端架構:從入門到微前端
- 黃峰達
- 3220字
- 2019-09-21 00:53:49
2.4 成長優化期:技術債務與演進
經歷一兩次上線后,項目進入一個穩定上線、交付的階段。筆者將其稱為成長優化期,這是一個技術提升開發體驗,技術帶來更多業務價值的階段。不過,實際上這已經是一個穩定的時期——我們可以抽時間來解決各種各樣的問題。
在設計架構和完善業務的過程中,會暴露出團隊的一系列問題:架構不完善、開發流程不便利等。短期內,這些問題并不會影響我們的開發。然而就長期而言,這些問題還是有可能會影響開發進度。先前我們在追趕業務時也遺留了一些技術問題,尤其是代碼的質量問題,這些問題會隨著時間的推移和業務代碼的堆砌變得越來越嚴峻。當然,如果一個項目的時間短,那么它就不會遇到這些挑戰。不論怎樣,程序員作為一個“匠人”,總得有點“追求”,要不斷地提高自己的水平。
2.4.1 償還技術債務
在技術準備期,我們在構建技術基礎方面花費了大量時間;在業務回補期,我們在支持業務的開發方面花費了大量時間。在這兩個時期,我們都或多或少地采取了一些妥協方案,為的是能加快速度開發流程。這些問題都將在未來成為我們的開發負擔。這種方式就和債務一樣,可以在短期內得到好處,但是在未來必須償還它們。這部分開發負擔,我們稱其為技術債。
技術債包含的內容有如下幾個方面:
(1)代碼質量。常見的問題有接口、函數的重復實現,即a成員在自己的功能內,實現過這個功能,但是b成員又實現一份,沒有提取到公共的方法中。實現方式或模式不統一,比如我們采用某個框架來解決問題,但是在真實場景下,可能又會采取過去的實踐方式,諸如使用Lambda、RxJava、Rx.js、Ramda等框架來進行函數式編程。少部分代碼未按規范進行實踐,這個問題更加常見,不僅存在于沒有代碼檢視(Code Review)的項目,還存在于擁有代碼檢視的項目。未檢視過的代碼,往往容易被遺漏。
(2)測試覆蓋率。在面對技術不熟悉、業務又過急的情況時,UI自動化測試、單位測試往往是最先被拋棄的一環。一方面,自動化測試的目的在于,保持功能不被破壞;另一方面,大部分的項目都擁有專業的測試人員進行測試。值得商榷的是,國內的互聯網公司都不會有測試這種東西,所以它們就存在這種長期的債務了。
(3)依賴問題。依賴對于短期項目來說不是問題,對于長期項目來說,依賴沒有及時更新是一個很嚴重的問題。例如,我們使用Redux 3.0的版本,當4.0版本發布的時候,按照語義化版本的規則,它可能修改了大量的代碼,而我們不得不追隨這個變化——除非,我們決定在未來重寫該應用,否則大量依賴過舊的問題,會導致我們難以對代碼進行重構,因而不得不重寫應用。
有些問題不是一天兩天造成的,比如測試覆蓋率低的問題,要解決這些問題也不是兩三天就能完成的。這往往需要制定一個長期的計劃,才能將它們一個一個地修正過來。多數情況下,我們并沒有足夠的時間在短期內修復,相關的技術債都是項目在不斷演進的過程中,一步步提升的。比如測試覆蓋率,它依賴所有的人編寫測試,并不斷限定測試覆蓋率的下限。這樣一來,在經歷了幾個迭代之后,我們便會擁有不錯的測試覆蓋率。
想要改善代碼質量也不是一件容易的事,改進代碼質量要依賴開發人員的水平,以及團隊的能力。如果團隊中的代碼質量不忍直視,充滿了各種Code Smell(代碼的壞味道),那么可以通過代碼檢視的方式來不斷提高團隊成員的水平。然后,有針對性地進行相應的培訓。
值得注意的是,與日常的業務代碼編寫相比,改進過去的代碼會帶來更多的成長和技術挑戰——我們更容易從錯誤的代碼中學習,而不是從成功的經驗中學習。舉個例子,我們直接看別人寫的與設計模式相關的代碼,并不會直接學到相關的內容,如果從過去寫的代碼重構看到設計模式,就能更深刻地理解相應的技術實踐。
2.4.2 優化開發體驗
提升開發體驗,也是在穩定時期值得考慮的另外一個因素。在我們的日常工作中,有很多是手動完成的,我們可以通過自動化來減少重復性工作。
有這樣一些例子:我們想創建一些測試數據,需要在數據庫中手動創建,但是缺少對應的批量創建腳本或命令;在調試時經常需要手動輸入相關的賬號,這也可以通過插件來自動化登錄;在開發移動應用時,一旦提交了代碼,就應該有相應的工具來自動化構建應用,并在構建成功后上傳到某個包管理中心,同時安裝到對應的測試機。相似的,還有其他各種方式的自動化流程,它們所做的便是減少重復的工作,以不斷提升開發體驗。
在這個過程中,我們可能寫了大量的接近重復的代碼。而這些代碼表面看上去并不是重復的,但是從抽象層來看是重復的。為了應對這種問題,我們可以進行代碼重構,也可以通過諸如創建領域特定語言的方式來進一步抽象出內部DSL的代碼。這樣,我們就可以減少花費在業務代碼上的時間。
此外,還可以思考怎樣將一些代碼的編寫實現自動化,例如在UI層通過采用Sketch2Code來生成模板頁面,或者編寫相應的拖曳生成UI界面的工具。一旦我們優化了這些開發流程——尤其在相應的自動化功能完成之后,開發人員就開始面對一些新的挑戰。
2.4.3 帶來技術挑戰
堆砌業務代碼對大部分技術人員來說是一件難熬的事情——每天都在重復工作,總想尋找一些挑戰。而對于周期長的應用來說,這種事情更為可怕。不論怎樣的項目,技術人員都需要獲得一定的能力增長。如果不能滿足這種訴求,那么就不能進步,也就相當于是一種能力方面的退步。因為技術在不斷地進步,新的技術總會很快地淘汰舊的技術。一個長期項目一旦結束,新的技術體系就可能與舊的技術體系完全不一樣了。
面對這種情況,一種常見的做法是引入新的技術棧。它既可以是一個框架,又可以是一門語言。我們可以引入Rxjs進行響應式(Reactive)開發,或者引入Rambda.js進行函數式編程。也可以采用新的語言,如在開發Android應用時,除了使用C++,還可以使用Kotlin語言來完成部分功能;在開發iOS應用時,可以結合Objective-C來使用Swift語言進行開發。
對于前端應用而言,我們還可以嘗試使用新的前端框架。目前維護大型的前端應用用的是Angula框架,如果要開發一些新的簡易的應用,那么可以嘗試使用React或者Vue框架來實現。
此外,對于稍有余力的項目團隊來說,可以嘗試進行一些小型的模擬項目。在這些小型的模擬項目上,我們關注于使用新的技術來開發現有的應用。一方面,可以為以后的架構演進做準備;另一方面,讓我們的技術與主流接軌。在筆者經歷過的項目中,曾經有一個項目,每隔幾個月開展一次Hackday活動,活動內容是使用新技術來重寫舊的應用。此外,我們還會做一些Workshop來練習使用項目潛在的新技術棧。
當然,對于項目管理者來說,堆砌業務代碼會輕松很多,畢竟出現新技術風險的可能性較低。但是在保證進度的情況下,也需要適當地帶來一些成長的機會。
2.4.4 架構完善及演進
經過大量的業務沉淀,我們也會發現架構中存在的一些問題。這些問題大都是一些架構設計上的偏差,經過調整,有的糾正到原來的設計上,有的使用新的架構設計。如果使用新的設計,我們還需要將那些使用過去設計的部分進行相應的修改,這意味著會有一定的返工工作量。
我們還可能遇到由于業務變更導致架構需要修改的情況,這時不僅需要調整,還需要進行全新的設計,才能適應新的業務需求。比如,如果在前端應用中包含大量的第三方應用,就要考慮插件化的方案,設計一個新的插件化架構。再舉一個例子,如果我們最開始開發應用的時候使用Cordova的WebView容器,那么出于安全考慮,會遷移到自己開發的混合應用框架上。這兩個例子都只是在架構設計上發生一些改變,但是從代碼的角度來看,所需要的修改并不會太多。
當然,這也并不是說我們預先設計的架構有問題,因為最初的架構滿足的是創建架構時設計的業務場景。當業務發生一些變化時,架構也需要做相應的調整。我們不可能讓過去的架構一直適應新的業務變化,在變化發生的時候演進系統的架構才是一種正常的形態。
此外,在更低級別的代碼層里,我們會發現代碼中存在一些復雜、混亂的相互調用,如果不加以規范并重構代碼,那么會使得新增的代碼加劇這個問題的產生。比如在項目中后期參與開發的人員編寫的代碼,可能與我們最初的架構風格不一致。如果原先的架構風格更符合需求,那么還需要幫助這些后來的開發人員解決相關的問題。