微信 Android 模塊化架構(gòu)重構(gòu)實(shí)踐(下)
上篇:http://zhuanlan.51cto.com/art/201708/547499.htm
取舍和選擇
對(duì)于架構(gòu)重構(gòu),我們也曾放眼行業(yè)內(nèi)已經(jīng)發(fā)布過(guò)的各種方案,希望從中找到一些解決思路。
我們參考了很多業(yè)界開(kāi)放和發(fā)表的架構(gòu)設(shè)計(jì)。總的來(lái)說(shuō),目前Android端App整體架設(shè)計(jì)上,除了聚焦在“大前端”之外,基本上都在“插件化/應(yīng)用沙盒”上面下功夫??梢詤⒖既鏰tlas、small、DroidPlugin、DynamicApk等等方案,不難發(fā)現(xiàn)讓模塊最終具備動(dòng)態(tài)性是它們最核心的能力。
“大前端”是個(gè)熱門(mén)的方向。我們結(jié)合自身情況,考慮到遷移已有代碼不是一件容易事,讓團(tuán)隊(duì)徹底切換編程工具短期不現(xiàn)實(shí)。這種“遠(yuǎn)水解不了近渴”的感覺(jué),也只能暫且作罷。
我們把目光投向了“插件化/應(yīng)用沙盒”。沙盒的好處是它具備很強(qiáng)的隔離性,有些方案可以徹底隔離代碼和資源,邊界限制性極強(qiáng),效果很好。此外,多數(shù)方案也都具備動(dòng)態(tài)更新能力、補(bǔ)丁能力,動(dòng)態(tài)性基本成為標(biāo)配。所以這時(shí)我們?cè)诳紤]需不需要走上這樣的路。
一切從自身情況出發(fā)。在微信的角度看,我們最關(guān)心的問(wèn)題是如何能約束住代碼邊界,如何防止架構(gòu)劣化,如何提高開(kāi)發(fā)效率。這樣的情況下,重新審視了具備動(dòng)態(tài)性的插件化和沙盒方案。先從動(dòng)態(tài)性上考慮,回看業(yè)內(nèi)的使用經(jīng)驗(yàn),目前的動(dòng)態(tài)性通常有兩個(gè)主要的用途:1)作為熱補(bǔ)丁使用;2)業(yè)務(wù)并行發(fā)布需求強(qiáng)烈,例如運(yùn)營(yíng)需求,作為快速發(fā)布的手段。對(duì)于第二點(diǎn),就目前的微信團(tuán)隊(duì)來(lái)說(shuō)是較少遇到的弱需求。相較之下,工具類(lèi)和電商類(lèi)應(yīng)用常有符合的使用場(chǎng)景。而對(duì)于***點(diǎn),我們則期望在實(shí)現(xiàn)模塊化上,目標(biāo)相對(duì)純粹一些,專(zhuān)心解決代碼自身問(wèn)題,之后再通過(guò)tinker在開(kāi)發(fā)階段之外實(shí)現(xiàn)熱補(bǔ)丁上的動(dòng)態(tài)性需要。
再來(lái)看隔離性。需要說(shuō)明的是,這里的隔離型主要來(lái)自于沙盒架構(gòu)效果。對(duì)于非獨(dú)立插件化工程(需要編譯依賴其他插件)除了動(dòng)態(tài)性,它的作用和普通module在代碼隔離上沒(méi)有區(qū)別,不做討論。
我們心里很期盼代碼與代碼之間那種干凈清爽的分割效果,但事與愿違。
圖20 - 模塊依賴示意圖
從上面這張模塊依賴示意圖看到,微信業(yè)務(wù)模塊之間數(shù)據(jù)關(guān)系相當(dāng)復(fù)雜,模塊間相互訪問(wèn)數(shù)據(jù)、共享某些功能的行為如此普遍。而實(shí)際情況比示意圖更麻煩。
面對(duì)微信業(yè)務(wù)數(shù)據(jù)相互間頻繁的調(diào)用,沙盒隔離容易導(dǎo)致代碼復(fù)用困難和相互調(diào)用麻煩,想在微信上實(shí)現(xiàn),目前困難極大。重構(gòu)難度不談,單是隔離的收益就很有可能無(wú)法彌補(bǔ)開(kāi)發(fā)效率上的損失。
從開(kāi)發(fā)模式上看也是一樣。微信Android團(tuán)隊(duì)目前每個(gè)迭代大概三四十人參與,內(nèi)部溝通成本不算高到不可接受。通常開(kāi)發(fā)同學(xué)可能要同時(shí)開(kāi)發(fā)和修改幾個(gè)模塊并保證他們相互模塊獨(dú)立,同時(shí)又可能有頻繁的模塊間通信。這種時(shí)候,模塊調(diào)用方不方便顯得很重要。
此外還需要考慮的問(wèn)題,從幾個(gè)成熟方案中都能看到hook Android框架、修改aapt、替換或包裝android gradle plugin、代理組件等等設(shè)計(jì)。這些方案的復(fù)雜度和兼容性代價(jià),不能忽視。使用和維護(hù)它們需要仔細(xì)權(quán)衡。
所以思前想后我們?nèi)赃x擇了重走最開(kāi)始的模塊化之路。一切問(wèn)題的根本還是在那個(gè)說(shuō)的很多年的代碼邊界、解耦和內(nèi)聚上。只要有了清晰獨(dú)立的代碼模塊,才有將來(lái)其他變化的可能性。
代碼之外,架構(gòu)之內(nèi)
模塊負(fù)責(zé)人制度
有這樣一句話,“不被監(jiān)管的權(quán)利一定會(huì)發(fā)生腐敗” 。如果放到軟件開(kāi)發(fā)的行當(dāng)來(lái)說(shuō),就是“不被監(jiān)管的代碼也一定會(huì)發(fā)生劣化”。所以代碼應(yīng)該要接受“監(jiān)管”。
為了能長(zhǎng)期有效的保持代碼質(zhì)量,我們開(kāi)始執(zhí)行新的代碼審查機(jī)制——模塊負(fù)責(zé)人制度。
代碼審查的好處毋庸置疑。在這之前,微信由于業(yè)務(wù)發(fā)展快速,同學(xué)們經(jīng)常會(huì)變換需求的開(kāi)發(fā)方向。面對(duì)著業(yè)務(wù)模塊數(shù)量比人多的情況,開(kāi)發(fā)同學(xué)經(jīng)常一個(gè)人需要開(kāi)發(fā)多個(gè)模塊。也因此許多模塊被無(wú)數(shù)人維護(hù)過(guò),基礎(chǔ)的支撐工程更是如此。這種類(lèi)似游擊戰(zhàn)的方式,開(kāi)發(fā)效率很高,支撐了微信快速的研發(fā)節(jié)奏,但也導(dǎo)致了“無(wú)主代碼”特別多。大家缺少對(duì)代碼的“歸屬感”,也降低了改進(jìn)優(yōu)化模塊的欲望。
另外在這之前,代碼審查是由leader對(duì)申請(qǐng)回流主干的Merge Request進(jìn)行review,這導(dǎo)致效率較低且容易遺漏問(wèn)題。合理的代碼審查更應(yīng)該是全員性質(zhì)的。
模塊負(fù)責(zé)人制度嘗試改變這些現(xiàn)狀。通過(guò)大家認(rèn)領(lǐng)模塊,對(duì)模塊的代碼和設(shè)計(jì)負(fù)責(zé),對(duì)模塊對(duì)外提供的接口服務(wù)負(fù)責(zé),對(duì)其他人修改自己模塊的行為進(jìn)行監(jiān)督。這些情況明顯提高開(kāi)發(fā)同學(xué)的代碼所有感,改變大家修改優(yōu)化和修改代碼的動(dòng)機(jī)。
推動(dòng)模塊負(fù)責(zé)人制度,漸進(jìn)式的推動(dòng)了大范圍的代碼審查,這樣的方式很適合像微信這樣沒(méi)有從一開(kāi)始執(zhí)行全員性質(zhì)Code Review的項(xiàng)目。目前模塊負(fù)責(zé)人機(jī)制運(yùn)轉(zhuǎn)順利,代碼審查率和模塊認(rèn)領(lǐng)率都在提高。
重構(gòu)與開(kāi)發(fā)者心態(tài)的關(guān)系
在一個(gè)長(zhǎng)期沒(méi)有改進(jìn)的框架下,開(kāi)發(fā)者的習(xí)慣可能會(huì)逐步變成跟隨式、保守式的開(kāi)發(fā)。這大概可以被描述成“只要?jiǎng)e人這樣做,我也這樣做,哪怕這么樣的設(shè)計(jì)不好,但也不會(huì)錯(cuò)”。隨著心態(tài)逐漸普遍,另一種情形出現(xiàn):經(jīng)常能聽(tīng)到有同學(xué)吐槽一些代碼,卻更少看到代碼在被改進(jìn)。這說(shuō)明一些沉積的問(wèn)題不是沒(méi)有被大家發(fā)現(xiàn),只是沒(méi)有人愿意去修改。這種情況下代碼和框架會(huì)隨著時(shí)間變得越來(lái)越差,有些問(wèn)題逐漸變成“陳年舊病”。 面對(duì)這個(gè)問(wèn)題首先要說(shuō),這不是開(kāi)發(fā)者合格與否的問(wèn)題,實(shí)際上有想法的開(kāi)發(fā)人員有很多,但想將每個(gè)想法轉(zhuǎn)換成代碼并讓大家接受,并不是一件很容易的事。尤其在一個(gè)大框架下,嘗試改變的代價(jià)很大。如果他的主要任務(wù)不在改進(jìn)某些模塊上,那么很多想法***都無(wú)法變成現(xiàn)實(shí)。這也是為什么保守和跟隨的習(xí)慣會(huì)逐漸變的普遍。
保守的氣氛需要被打破。當(dāng)開(kāi)啟一次重構(gòu)之后,你會(huì)發(fā)現(xiàn)團(tuán)隊(duì)中會(huì)有很多積極的聲音響應(yīng),他們會(huì)把積壓的想法和意見(jiàn)拋出來(lái)。一次問(wèn)題的解決,可能會(huì)為另一個(gè)問(wèn)題的解決帶來(lái)機(jī)會(huì),其他開(kāi)發(fā)同學(xué)的一些想法也許就能更容易落地。所以不定期的推動(dòng)一些模塊的重構(gòu),將一些對(duì)代碼的不滿釋放出來(lái),是一種不錯(cuò)的激活。
此外在重構(gòu)之后,還要考慮引導(dǎo)開(kāi)發(fā)的代碼組織方式切換,多用模板、正確的代碼實(shí)例等,讓他們可以放心參考。
模塊劃分經(jīng)驗(yàn)談
維持代碼邊界
代碼的邊界就像一堵墻,架構(gòu)的劣化都是從這堵墻的瓦解開(kāi)始的。從以往的經(jīng)驗(yàn)來(lái)看,編譯上的隔離是***的約束手段,單純的約定或準(zhǔn)則并不能永遠(yuǎn)的保持下去。 所以在任何情況下都盡可能不要放開(kāi)編譯上的約束。接著,將接口和實(shí)現(xiàn)分離,其他工程只依賴接口而不依賴實(shí)現(xiàn),這樣的邊界效果更好。當(dāng)然破壞無(wú)處不在,例如遇到某個(gè)緊急需求要某模塊新增若干接口,就可能出現(xiàn)跳過(guò)接口直接依賴實(shí)現(xiàn)工程進(jìn)行開(kāi)發(fā)的情況。這時(shí)可以考慮通過(guò)代碼的審查進(jìn)行監(jiān)督,也可以通過(guò)開(kāi)發(fā)簡(jiǎn)單的編譯腳本,檢查是否有不當(dāng)依賴產(chǎn)生。
劃定模塊邊界的細(xì)節(jié)問(wèn)題
當(dāng)對(duì)代碼進(jìn)行解耦時(shí),即便大體上的模塊職責(zé)劃分已經(jīng)清晰,但因?yàn)槟K間的各種業(yè)務(wù)關(guān)系,細(xì)節(jié)上仍會(huì)遇到糾纏不清的情況。事實(shí)上,因?yàn)樾枨蠹肮δ艿牟煌](méi)有哪一種模塊劃分的規(guī)則是完全適用于每個(gè)應(yīng)用的。隨著業(yè)務(wù)的發(fā)展和變化,模塊邊界出現(xiàn)不合適的情況完全符合預(yù)期。
那么如何讓模塊劃分更讓大家覺(jué)得合理,或者說(shuō)當(dāng)遇到一個(gè)兩難選擇時(shí),按照什么樣的方式大家會(huì)更好理解?我們建議的方法其實(shí)也很簡(jiǎn)單:試著對(duì)代碼“講一個(gè)符合邏輯的故事”,哪個(gè)故事講得通,你就可以將之作為拆分的選擇。因?yàn)榇a解耦從來(lái)不是問(wèn)題,糾結(jié)的只是解耦行為能不能讓人理解。例如一些模塊間通信用的數(shù)據(jù)結(jié)構(gòu)究竟屬于那個(gè)模塊的問(wèn)題就可以用這種方式仲裁。在糾結(jié)的時(shí)候,能自圓其說(shuō)的方案往往就足夠了。我們要盡力避免的,應(yīng)該是隨意拼湊和單純?yōu)榱祟?lèi)型解耦而解耦的情況。
模塊的一般組織方式
設(shè)計(jì)一個(gè)模塊,我們有一個(gè)一般性的組織方式,可以將模塊分成三個(gè)工程:implementation工程、api工程、library工程。
implementation工程提供邏輯的實(shí)現(xiàn)。api工程提供對(duì)外的接口和數(shù)據(jù)結(jié)構(gòu)。library工程,則提供該模塊的一些工具類(lèi)。
從另一個(gè)角度看,implementation工程實(shí)際上是和應(yīng)用的狀態(tài)、生命周期相關(guān)的,它的執(zhí)行依賴于各種應(yīng)用狀態(tài)。而library工程則不關(guān)心這些狀態(tài)。因此也可以看做library提供某種功能,implementation則是如何運(yùn)用這種功能。例如,我們實(shí)現(xiàn)一個(gè)表情模塊,library工程提供表情的資源、表情的渲染和播放能力,api工程提供了使用表情的服務(wù)接口,implementation工程則提供了api的實(shí)現(xiàn),及何時(shí)開(kāi)始加載表情資源、緩存管理、以及其他表情功能例如商店等等。
當(dāng)然,這是一個(gè)指導(dǎo)性的建議,很多時(shí)候library工程和api工程之間沒(méi)有明顯邊界也很正常。但強(qiáng)烈建議至少要有implementation工程和api工程。
分析依賴關(guān)系的工具
解耦代碼時(shí),快速分析代碼的依賴關(guān)系能很好的提升工作效率。Android Studio提供了一個(gè)不錯(cuò)的工具。
圖21 - Analyze dependency工具
文件、資源以及工程,都可以進(jìn)行依賴分析。有了分析結(jié)果,接下來(lái)一步一步把代碼分離就簡(jiǎn)單多了。
***
重構(gòu)整體架構(gòu)不是一件容易事,通常也不太可能讓整個(gè)團(tuán)隊(duì)停下來(lái)只做重構(gòu)。所以一直以來(lái)微信的重構(gòu)都是隨著版本迭代進(jìn)行“拆分”-> “灰度” -> “回流”的循環(huán)節(jié)奏。
“設(shè)計(jì)系統(tǒng)的組織,其產(chǎn)生的設(shè)計(jì)和架構(gòu)等價(jià)于組織間的溝通結(jié)構(gòu)”。對(duì)于微信幾年間走過(guò)的路程,時(shí)至今日?qǐng)F(tuán)隊(duì)內(nèi)的溝通形式還可以做到較多的直接溝通。這些情況決定了微信如今的技術(shù)選擇。所以在方案選擇上我們就更愿意尋求相對(duì)簡(jiǎn)單合適的方式解決問(wèn)題——用純粹的模塊化保持后續(xù)架構(gòu)的靈活性和健壯性,重新強(qiáng)調(diào)依賴、強(qiáng)調(diào)應(yīng)用狀態(tài)和生命周期、強(qiáng)化代碼的邊界。
除了代碼上的設(shè)計(jì),代碼之外我們也做了些努力。我們認(rèn)同代碼審查的意義,也開(kāi)始推行模塊負(fù)責(zé)人的審查機(jī)制。此外我們還打算強(qiáng)化文檔的使用,當(dāng)然這個(gè)還在規(guī)劃中。
原文鏈接:https://www.qcloud.com/community/article/794491,作者:carlguo
【本文是51CTO專(zhuān)欄作者“騰訊云技術(shù)社區(qū)”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)51CTO聯(lián)系原作者獲取授權(quán)】

















 
 
 









 
 
 
 