天天看點

微信 Android 子產品化架構重構實踐(下)

作者:carlguo

接上篇:《微信 Android 子產品化架構重構實踐(上)》

對于架構重構,我們也曾放眼行業内已經釋出過的各種方案,希望從中找到一些解決思路。

我們參考了很多業界開放和發表的架構設計。總的來說,目前Android端App整體架設計上,除了聚焦在“大前端”之外,基本上都在“插件化/應用沙盒”上面下功夫。可以參考如atlas、small、DroidPlugin、DynamicApk等等方案,不難發現讓子產品最終具備動态性是它們最核心的能力。

“大前端”是個熱門的方向。我們結合自身情況,考慮到遷移已有代碼不是一件容易事,讓團隊徹底切換程式設計工具短期不現實。這種“遠水解不了近渴”的感覺,也隻能暫且作罷。

我們把目光投向了“插件化/應用沙盒”。沙盒的好處是它具備很強的隔離性,有些方案可以徹底隔離代碼和資源,邊界限制性極強,效果很好。此外,多數方案也都具備動态更新能力、更新檔能力,動态性基本成為标配。是以這時我們在考慮需不需要走上這樣的路。

一切從自身情況出發。在微信的角度看,我們最關心的問題是如何能限制住代碼邊界,如何防止架構劣化,如何提高開發效率。這樣的情況下,重新審視了具備動态性的插件化和沙盒方案。先從動态性上考慮,回看業内的使用經驗,目前的動态性通常有兩個主要的用途:1)作為熱更新檔使用;2)業務并行釋出需求強烈,例如營運需求,作為快速釋出的手段。對于第二點,就目前的微信團隊來說是較少遇到的弱需求。相較之下,工具類和電商類應用常有符合的使用場景。而對于第一點,我們則期望在實作子產品化上,目标相對純粹一些,專心解決代碼自身問題,之後再通過tinker在開發階段之外實作熱更新檔上的動态性需要。

再來看隔離性。需要說明的是,這裡的隔離型主要來自于沙盒架構效果。對于非獨立插件化工程(需要編譯依賴其他插件)除了動态性,它的作用和普通module在代碼隔離上沒有差別,不做讨論。

我們心裡很期盼代碼與代碼之間那種幹淨清爽的分割效果,但事與願違。

微信 Android 子產品化架構重構實踐(下)

圖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提供了一個不錯的工具。

微信 Android 子產品化架構重構實踐(下)

圖21 - Analyze dependency工具

檔案、資源以及工程,都可以進行依賴分析。有了分析結果,接下來一步一步把代碼分離就簡單多了。

重構整體架構不是一件容易事,通常也不太可能讓整個團隊停下來隻做重構。是以一直以來微信的重構都是随着版本疊代進行“拆分”-> “灰階” -> “回流”的循環節奏。

“設計系統的組織,其産生的設計和架構等價于組織間的溝通結構”。對于微信幾年間走過的路程,時至今日團隊内的溝通形式還可以做到較多的直接溝通。這些情況決定了微信如今的技術選擇。是以在方案選擇上我們就更願意尋求相對簡單合适的方式解決問題——用純粹的子產品化保持後續架構的靈活性和健壯性,重新強調依賴、強調應用狀态和生命周期、強化代碼的邊界。

除了代碼上的設計,代碼之外我們也做了些努力。我們認同代碼審查的意義,也開始推行子產品負責人的審查機制。此外我們還打算強化文檔的使用,當然這個還在規劃中。

最後希望我們分享的一點經驗能對大家有些價值,歡迎留言交流。