深圳外文網(wǎng)站制作交換免費(fèi)連接
一、前言
大多數(shù)的技術(shù)研發(fā)都對(duì)重構(gòu)有所了解,而每個(gè)研發(fā)又都有自己的理解。從代碼重構(gòu)到架構(gòu)重構(gòu),我參與了攜程大型全鏈路重構(gòu)項(xiàng)目,積累了一點(diǎn)經(jīng)驗(yàn)心得,在此拋磚引玉和大家分享。
二、重構(gòu)的定義
重構(gòu)是指在不改變外部行為的情況下,改進(jìn)其內(nèi)部結(jié)構(gòu)的軟件系統(tǒng)更改過程。
三、重構(gòu)的原因
3.1 組織架構(gòu)調(diào)整
目前攜程大部分業(yè)務(wù)場(chǎng)景都使用了微服務(wù)架構(gòu),要求服務(wù)應(yīng)該封裝單一的責(zé)任或單一的能力,以形成松散耦合的服務(wù)架構(gòu)。
根據(jù)著名的康威定律,保證一個(gè)團(tuán)隊(duì)可以獨(dú)立工作、快速交付變更、盡可能消除團(tuán)隊(duì)之間協(xié)作和協(xié)調(diào)的費(fèi)力度。
所以當(dāng)組織架構(gòu)因?yàn)闃I(yè)務(wù)發(fā)展需要做相應(yīng)調(diào)整時(shí),決定了服務(wù)的架構(gòu)也會(huì)需要相應(yīng)的重構(gòu)。
3.2 提升研發(fā)效能
重構(gòu)遺留代碼的目的,通常是使系統(tǒng)其更易于維護(hù),減少維護(hù)系統(tǒng)所需的工作量,釋放出研發(fā)資源處理更高價(jià)值的任務(wù)。
即使是在項(xiàng)目成功落地之后,仍然需要持續(xù)的系統(tǒng)迭代,支持新的業(yè)務(wù)需求。迭代過程引入新的缺陷也是難以避免的。
即使是最好的團(tuán)隊(duì)也可能會(huì)提交不成熟的代碼,這往往會(huì)導(dǎo)致代碼復(fù)雜性讓系統(tǒng)變得難以維護(hù),長(zhǎng)期以往系統(tǒng)會(huì)越來越難以維護(hù),影響研發(fā)效能。越來越長(zhǎng)的開發(fā)周期,導(dǎo)致業(yè)務(wù)需求發(fā)布被推遲。
重構(gòu)可以減少構(gòu)建周期時(shí)間,改善需求交付時(shí)間。
3.3 償還技術(shù)債
技術(shù)債是很多研發(fā)團(tuán)隊(duì)為了追求交互的速度,傾向于選擇更簡(jiǎn)單但不太可靠的方案所付出的代價(jià)。短期內(nèi)為了更快地交付做出的妥協(xié)都會(huì)在未來帶來更多的工作量。
重構(gòu)有助于消除開發(fā)人員積累的技術(shù)債務(wù)的數(shù)量。
3.4 應(yīng)用現(xiàn)代化
遺留代碼可能無法利用現(xiàn)代 DevOps 集成來加速 CI/CD。
遺留代碼需要改造成無狀態(tài)服務(wù),這樣可以適配云上的彈性伸縮,優(yōu)化成本。
四、重構(gòu)的影響面
從重構(gòu)的影響面可以分為最高層次的架構(gòu)重構(gòu)和最低層次的代碼重構(gòu)。
代碼重構(gòu)是在修改代碼后不會(huì)改變對(duì)外的行為,主要目的是提高整個(gè)軟件的質(zhì)量。一個(gè)重構(gòu)通常是一個(gè)小任務(wù),但是多個(gè)重構(gòu)應(yīng)用于代碼可以顯著提高其質(zhì)量。
架構(gòu)重構(gòu)主要是將現(xiàn)有代碼重新組織成新的層級(jí),改變代碼在邏輯層次中的組織方式。
4.1 架構(gòu)重構(gòu)和代碼重構(gòu)的比較?
代碼重構(gòu)通常在一個(gè)開發(fā)團(tuán)隊(duì)內(nèi)部達(dá)成一致,影響的范圍容易控制和驗(yàn)證,風(fēng)險(xiǎn)較低。
架構(gòu)重構(gòu)必須要多個(gè)團(tuán)隊(duì)甚至是技術(shù)委員會(huì)達(dá)成一致,影響范圍大,風(fēng)險(xiǎn)和成本較高。通常在做架構(gòu)重構(gòu)前嘗試用可量化的指標(biāo)來確定投入產(chǎn)出比,決策是否值得重構(gòu)。
此外架構(gòu)重構(gòu)相比代碼重構(gòu)難度更高,對(duì)研發(fā)的專業(yè)知識(shí)要求也會(huì)更高,項(xiàng)目組里至少需要有一個(gè)熟悉鏈路上所有服務(wù)的資深研發(fā),負(fù)責(zé)重構(gòu)決策(特別是服務(wù)和服務(wù)邊界處)。
五、重構(gòu)計(jì)劃
在開始重構(gòu)編碼之前,對(duì)現(xiàn)有系統(tǒng)深入分析,評(píng)估哪些部分應(yīng)該被重構(gòu)以及重構(gòu)的優(yōu)先級(jí),有助于列出重構(gòu)計(jì)劃。
全鏈路上的大規(guī)模重構(gòu)往往都是長(zhǎng)期項(xiàng)目,初期制定的計(jì)劃也肯定會(huì)有變化調(diào)整,特別是重構(gòu)過程中會(huì)有新的業(yè)務(wù)需求進(jìn)入,需要調(diào)整優(yōu)先級(jí),所以計(jì)劃要分成不同的階段性里程碑目標(biāo)。
六、業(yè)務(wù)研發(fā)團(tuán)隊(duì)溝通
重構(gòu)期間需要和負(fù)責(zé)業(yè)務(wù)需求的研發(fā)團(tuán)隊(duì)同步協(xié)調(diào)計(jì)劃,優(yōu)先對(duì)新需求部分重構(gòu),避免新功能完成后立即又成為重構(gòu)任務(wù)。
例如,重構(gòu)后的版本在下周就會(huì)發(fā)布運(yùn)行,有些業(yè)務(wù)需求計(jì)劃可以直接在新版本上實(shí)現(xiàn),這樣既避免了業(yè)務(wù)研發(fā)被技術(shù)債務(wù)困擾,也減少了重構(gòu)項(xiàng)目組的工作量。
七、分層設(shè)計(jì)
分層設(shè)計(jì)即定義服務(wù)之間、模塊之間的邊界,是完成重構(gòu)的關(guān)鍵,讓代碼之間的依賴關(guān)系變得更清晰,減少耦合性。
根據(jù)關(guān)注點(diǎn)分離原則,把現(xiàn)有的功能分類,將對(duì)應(yīng)的代碼移動(dòng)到合適的層級(jí)和該層級(jí)對(duì)應(yīng)的模塊。
前提是需要理解每個(gè)類的功能是什么,當(dāng)面對(duì)實(shí)現(xiàn)多個(gè)功能的類,必須要拆分成多個(gè)只服務(wù)于一個(gè)目的的類。
單一職責(zé)的類更具備可測(cè)試性,依賴更少的外部功能,減少測(cè)試用例里 Mock 的工作量。單一職責(zé)的類也更容易被復(fù)用,更容易維護(hù)。
大型重構(gòu)項(xiàng)目會(huì)有許多工作要做,分層后的設(shè)計(jì)就像地圖,避免研發(fā)迷失方向。
八、灰度
在分階段重構(gòu)的過程中,引入抽象層(例如面向接口編程),同一個(gè)接口有新老兩套實(shí)現(xiàn)。抽象層的引入允許顯示系統(tǒng)里新老版本多個(gè)實(shí)現(xiàn)的共存。
這種看似額外的臨時(shí)兼容工作,可以帶來以下好處:
-
我們?cè)诰帉憸y(cè)試用例代碼的時(shí)候,也盡量面向接口驗(yàn)證,這樣在舊代碼驗(yàn)證通過的案例能夠運(yùn)行在重構(gòu)后的新代碼上。
-
使用特性開關(guān)控制新老版本實(shí)現(xiàn)的切換,可以在不破壞原有的功能且對(duì)調(diào)用方透明的前提下逐步灰度在線上版本進(jìn)行重大更改,最后新版本實(shí)現(xiàn)穩(wěn)定后,刪除老代碼的實(shí)現(xiàn)。這種方式也能減少研發(fā)在重構(gòu)時(shí)候的心智負(fù)擔(dān)。
-
特性開關(guān)的引入也能夠?qū)崿F(xiàn)快速回退,控制因?yàn)樾聦?shí)現(xiàn)帶來的破壞范圍。但驗(yàn)證不能完全依賴線上業(yè)務(wù)流量(對(duì)業(yè)務(wù)有損),下文會(huì)提到重構(gòu)版本在發(fā)布前的改善質(zhì)量的方法。
九、改善質(zhì)量
9.1 靜態(tài)檢查
靜態(tài)檢查工具 Sonar
-
解決循環(huán)依賴
-
解決 Cyclomatic Complexity?
-
控制每個(gè)類的方法數(shù)
-
控制每個(gè)方法的行數(shù)
-
解決 warning(刪除不使用的代碼,也意味著減少重構(gòu)的工作量)
9.2 自動(dòng)化測(cè)試
自動(dòng)化測(cè)試是重構(gòu)最重要的基礎(chǔ),它能確保系統(tǒng)的行為不會(huì)因?yàn)橹貥?gòu)而改變。
在重構(gòu)開始優(yōu)先保證測(cè)試的覆蓋率,所以我們會(huì)在重構(gòu)前優(yōu)先使用集成測(cè)試來驗(yàn)證重構(gòu)結(jié)果:
-
集成測(cè)試相比單元測(cè)試覆蓋率會(huì)更大,隨著覆蓋率越高,覆蓋剩余的測(cè)試成本也會(huì)越大,例如一些異常邊界場(chǎng)景,用單元測(cè)試覆蓋的成本會(huì)更低。
-
有些單元測(cè)試往往和老系統(tǒng)代碼耦合,沒有面向接口測(cè)試,或者是一些靜態(tài)類的測(cè)試。重構(gòu)代碼意味著單元測(cè)試需要重寫,在早期重構(gòu)階段優(yōu)先用集成測(cè)試覆蓋驗(yàn)證。
設(shè)計(jì)集成測(cè)試需要減少對(duì)其他系統(tǒng)(尤其是和外部第三方交互)的依賴,通過一些?Mock?工具提供穩(wěn)定的測(cè)試數(shù)據(jù),讓測(cè)試結(jié)果更穩(wěn)定可靠,比如使用緩存保證相同的請(qǐng)求能拿到相同的結(jié)果,這也能幫助后續(xù)新老版本的比對(duì)回歸驗(yàn)證。
借助 CI 持續(xù)迭代,每一次變更提交會(huì)觸發(fā)自動(dòng)化測(cè)試回歸驗(yàn)證,在早期開發(fā)階段快速暴露問題。同時(shí)也鼓勵(lì)研發(fā)多次提交變更,避免一次提交包含太多的功能點(diǎn),減少測(cè)試不通過排查問題的復(fù)雜度。
9.3 鏈路比對(duì)
在整個(gè)鏈路上的服務(wù)完成一次里程碑后,會(huì)做鏈路上的端到端的自動(dòng)化測(cè)試,比對(duì)新老系統(tǒng)結(jié)果,驗(yàn)證整條鏈路。
a. 線上版本鏈路入口在收到業(yè)務(wù)真實(shí)請(qǐng)求后,會(huì)把請(qǐng)求和響應(yīng)推送到消息隊(duì)列里。
b. 比對(duì)工具 SmartDiff 訂閱消息隊(duì)列,復(fù)制請(qǐng)求重構(gòu)后的版本鏈路入口,拿到結(jié)果后和線上版本結(jié)果比對(duì)
c. SmartDiff 把比對(duì)結(jié)果寫入 ES 日志,研發(fā)分析日志排查比對(duì)差異原因,修復(fù)問題。
十、可觀察性
很多時(shí)候,軟件架構(gòu)容易被作為一個(gè)學(xué)術(shù)概念。即使是在一些成熟的研發(fā)團(tuán)隊(duì),團(tuán)隊(duì)成員能夠描述系統(tǒng)架構(gòu)應(yīng)該實(shí)現(xiàn)哪些重要的非業(yè)務(wù)功能需求(吞吐量、耗時(shí)、穩(wěn)定性等等),但很難證明系統(tǒng)確實(shí)做到了這些。
尤其是系統(tǒng)經(jīng)過長(zhǎng)期多次迭代后,當(dāng)前的架構(gòu)實(shí)現(xiàn)是否還遵循原先的架構(gòu)設(shè)計(jì)呢?如果無法驗(yàn)證,研發(fā)對(duì)架構(gòu)也會(huì)失去信心。
所以我們需要對(duì)系統(tǒng)定義多個(gè)非功能性指標(biāo)并將其可視化,線上持續(xù)地觀察和確認(rèn)是否和我們當(dāng)初設(shè)計(jì)期望的指標(biāo)一致。
比如讀寫緩存接口響應(yīng) P90 保持在 100ms 以內(nèi),假設(shè)后續(xù)為了提升緩存命中率,架構(gòu)重構(gòu)引入了多個(gè)緩存,P90 線 100ms 就可能會(huì)不達(dá)標(biāo)。
指標(biāo)可視化幫助我們快速發(fā)現(xiàn)新的設(shè)計(jì)帶來的問題并做相應(yīng)調(diào)整。這些非功能性指標(biāo)就如同測(cè)試用例一樣,快速發(fā)現(xiàn)問題、增強(qiáng)研發(fā)對(duì)架構(gòu)重構(gòu)的信心。