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