1. 程式人生 > >微信 Android 模組化架構重構實踐

微信 Android 模組化架構重構實踐

微信Android架構歷史

微信Android誕生之初,用的是常見的分層結構設計。這種架構簡單、清晰並一直沿襲至今。這是微信架構的v1.x時代。

圖1-架構演進

到了微信架構的v2.x時代,隨著業務的快速發展,訊息通知不及時和Android 2.3版本之前webview記憶體洩露問題開始突顯。由於程式碼、記憶體、apk大小都在增長,對系統資源的佔用越來越多,導致微信程序容易被系統回收。因此微信開始轉向多程序架構,獨立的通訊程序保持長連線的穩定性,獨立的webview程序也阻隔了記憶體洩露導致的問題。

時間繼續推進,我們也遇到了65535問題和LinearAlloc問題。這時的微信已經具備了許多功能像朋友圈、搖一搖、附近的人等等,分離核心功能和其他業務模組變得越發重要。為此,微信開啟了第三次架構改造(v3.x)。我們對各種產品功能進行解耦並拆分到相互獨立的p_xxx工程中,這是微信第一次進行模組化架構的重構。經過幾個月的努力,微信拆出了幾十個p工程,它們都通過基礎元件訪問網路、儲存等服務,互相獨立並行。新的p工程架構支撐了微信更快速的業務發展,配合多分支開發模式的改進,能夠支援團隊多分支多team的並行開發。

圖2 - 架構圖

為何再次重構微信

PS:有興趣的加入Android工程師交流QQ群:752016839 主要針對Android開發人員提升自己,突破瓶頸,相信你來學習,會有提升和收穫。

原本好好的架構出了什麼問題?

從上個架構之後的兩年多時間裡,微信Android基本沒有大的架構改動。配合gradle的編譯,以及git的多分支並行開發,微信的模組工程數量不斷增多,支撐了遊戲、支付等大功能,可以說這段時間裡原有架構起到了很好的作用。

然而隨著程式碼繼續膨脹,一些問題開始突顯出來。首先出問題的是基礎工程libnetscene和libplugin。基礎工程一直處於不斷膨脹的狀態,同時主工程也在不斷變大。同時基礎工程存在中心化問題,許多業務Storage類被附著在一個核心類上面,久而久之這個類已經沒法看了。此外當初為了平滑切換到gradle避免結構變化太大以及太多module,我們將所有工程都對接到一個module上。缺少了編譯上的隔離,模組間的程式碼邊界出現一些劣化。雖然緊接著開發了工具來限制模組間的錯誤依賴,但這段時間裡的影響已經產生。在上面各種問題之下,許多模組已經稱不上“獨立”了。所以當我們重新審視程式碼架構時,以前良好模組化的架構設計已經逐漸變了樣。

圖3 - 架構逐漸的變化

“君有疾在腠理,不治將恐深”,在我們還在猶豫到底要不要重構的時候,硬體同學向我們提出了需求。希望將微信Android程式碼移植到類似微信相簿這樣產品中。這樣就可以快速跟進微信業務最新的支撐元件、協議、安全性、後臺服務等能力,而且程式碼要儘可能精簡,可以選擇和定製模組,可以移植模組來實現原型嘗試。但就之前的情況來說,微信一時難以滿足。這下定了,還得重構。

於是我們回過頭仔細看之前的設計,找找問題究竟是怎麼來的。

問題出在哪

先尋找程式碼膨脹的原因。

翻開基礎工程的程式碼,我們看到除了符合設計初衷的儲存、網路等支援元件外,還有相當多的業務相關程式碼。這些程式碼是膨脹的來源。但程式碼怎麼來的,非要放這?一切不合理皆有背後的邏輯。在之前的架構中,我們大量適用Event事件匯流排作為模組間通訊的方式,也基本是唯一的方式。使用Event作為通訊的媒介,自然要有定義它的地方,好讓模組之間都能知道Event結構是怎樣的。這時候基礎工程好像就成了存放Event的唯一選擇——Event定義被放在基礎工程中;接著,遇到某個模組A想使用模組B的資料結構類,怎麼辦?把類下沉到基礎工程;遇到模組A想用模組B的某個介面返回個數據,Event好像不太適合?那就把程式碼下沉到基礎工程吧……

就這樣越來越多的程式碼很“自然的”被下沉到基礎工程中。

我們再看看主工程,它膨脹的原因不一樣。分析一下基本能確定的是,首先作為主幹業務一直還有需求在開發,膨脹在所難免,缺少適當的內部重構但暫時不是問題的核心。另一部分原因,則是因為模組的生命週期設計好像已經不滿足使用需要。之前的模組生命週期是從“Account初始化”到“Account已登出”,所以可以看出在這時機之外肯定還有邏輯。放在以前這不是個大問題,剛啟動還不等“Account初始化”就要執行的邏輯哪有那麼多。而現在不一樣,再簡單的邏輯堆積起來也會變複雜。此時,在模組生命週期外的邏輯基本上只能放主工程。

此外的問題,模組邊界破壞、基礎工程中心化,都是程式碼持續劣化的幫凶。

總之在模組化上我們忽視了一些重要的問題,必須重塑。

重塑模組化

重塑模組化,我們分解為三個目標:

  • 改變通訊方式

  • 重新設計模組

  • 約束程式碼邊界

改變通訊方式

前面講過,我們使用Event匯流排作為模組間通訊的媒介,這種設計很常見。然而當回顧整體程式碼時能發現,Event並非所有通訊需要的最佳形式。它的特點適合一對多的廣播場景,依賴關係弱。一旦遇到需要一組業務介面時,用Event寫起來那是十分痛苦的。也正因如此,這種情況下大家都跳過了Event的使用,直接將程式碼下沉到了基礎工程,共享程式碼,進而導致基礎工程的不斷膨脹。

所以選個合適的通訊方式很有必要,我們希望兼顧考慮開發的便利性和協議的約束性。

Event不合適。協議通訊如何?

我們理解的協議通訊,是指跨平臺/序列化的通訊方式,類似終端和伺服器間的通訊或restful這種。現在這種形式在終端內很常見了。協議通訊具備一種很強力解耦能力,但也有不可忽視的代價。無論什麼形式的通訊,所有的協議定義需要讓通訊兩方都能獲知。通常為了方便會在某個公共區域存放所有協議的定義,這情況和Event引發的問題有點像。另外,協議如果變化了,兩端怎麼同步就變得有點複雜,至少要配合一些框架來實現。在一個應用內,這樣會不會有點複雜?用起來好像也不那麼方便?更何況它究竟解決多少問題呢。

所以我們想要簡單點。經過權衡,我們決定用模組提供“SDK”的方式作為它與其他模組進行通訊的手段。

通常“SDK”提供的是什麼,是介面 + 資料結構。這種方式好處明顯:實現簡單也能解決問題,IDE容易補全、呼叫介面方便,不用配合工具,協議變化直接反映在編譯上,維護介面也簡單了。

其實想想,用協議的方式在終端內作為通訊手段,開發效率低,也容易出錯。因此可能會誕生各種框架和工具來提升這裡損失的效率。到頭來,是不是大家都實現了一套類似RPC這樣的封裝。其實本地的通訊,能用介面就挺好,不能用的時候,再用協議封裝也來得及。

確定了方案,實現起來就很簡單。我們的註冊方式和介面訪問都很簡單。用介面註冊,再用介面訪問,不暴露實現細節。如下圖。

圖4 - 註冊介面

圖5 - 訪問介面

接下來,怎麼暴露介面更方便?

模組暴露“SDK”的方式無非就是新建個“SDK”工程,剝離介面和資料結構到該工程裡面,然後讓其他模組引用編譯。但這樣有點麻煩,能不能再方便點?

當然有辦法。我們實現了另一種介面暴露的形式——“.api化”。

使用方式和思路都很簡單。對於java檔案,將工程裡想要暴露出去的介面類字尾名從“.java”改成“.api”,就可以了。

而且並不只是java檔案,其他檔案如果也想暴露,在檔名後增加".api”,也一樣可以。

圖6 - “.api化”

當然,要讓工程支援這樣的方式,gradle檔案肯定會有一點改變。

settings.gradle

build.gradle

圖 7 - settings.gradle & build.gradle

就這樣,可以說暴露介面變得非常容易,不用擔心實現類也被人引用到。而它的實現原理也相當簡單:自動生成一個“SDK”工程,拷貝.api字尾檔案到工程中就行了,後面其他工程依賴編譯的只是這個生成的工程。簡單好用。

還有個細節,如果想編輯.api字尾的java檔案,為了能讓Android Studio繼續高亮該怎麼辦?可以在File Type中把.api作為java檔案型別。

圖8 - 設定File Types

重新設計模組

要把模組重新設計,還要做好幾件事。首先,消滅程式碼經常下沉的“三不管區域”——基礎工程。這意味著原來的模組要把之前下沉的程式碼重新認領回去。

圖9 - 分層結構改造

為了鞏固替代基礎工程的mmkernel層,不被濫用為新的程式碼堆放處,順便還要解決中心化問題。就必須強化它的職責和設計。

mmkernel結構可以很通用的定義為CoreAccount/CoreNetwork/CoreStorage三個部分,分別提供了核心賬號狀態(初始化、登出)、網路狀態回撥(連結建立)、儲存狀態生命週期(db建立、銷燬、使用者儲存路徑切換、sdcard掛起)。 

圖10 

再然後是生命週期問題,我們需要重新設計正確的生命週期。

之前講過,我們的模組生命週期大體上只有“Account初始化”和“Account登出”兩個階段。這已經不夠用了。

所以擴大模組的生命週期,就給了模組實現各種程式碼需要的時機,才能避免大家往主工程塞程式碼。

圖11

實現新的生命週期是一個正確的選擇,同時產生了解決另一個問題的機會——複雜的啟動流程。

要知道主工程的程式碼一部分原因是啟動流程堆積造成的,邏輯多了程式碼自然多。隨之而來的問題就是程式碼多了,邏輯也就跟著複雜起來。微信的初始化邏輯是順序排列在一起並從上到下執行,某種情況下還會非同步啟動。當程式啟動流程比較複雜時,這樣的程式碼會產生“隱性依賴”的問題。“隱性依賴”顧名思義就是:原本並應該存在依賴的程式碼,隨著版本的迭代逐漸產生了依賴,而且還不明顯。這樣的情況會讓情況惡化,大家只敢往裡面堆程式碼,但卻不敢“亂動”。

所以重新設計的模組應該要徹底避免這些問題。

我們重新定義了模組的生命週期,將模組的生命週期延長到應用啟動和退出。而後,每個模組都可以定義一個Plugin類,作為模組的“支柱”或“起點”。作為解決初始化問題的手段,它具備幾個主要階段:dependency()configure()execute()

圖12 - plugin初始化的幾個階段

dependency()階段,用於設定需要依賴的其他Plugin,當然提供那個Plugin的別名介面類就可以了。

圖13 - 設定dependency

依賴階段我們會生成整個模組的依賴樹。這與編譯時的依賴不同。通常的依賴關係是分為兩種的,一種是型別依賴也就是編譯期依賴,需要被依賴模組提供具體型別才能編譯通過;另一種依賴則是執行期的邏輯依賴或資料一致性依賴,當一個模組用這種方式依賴另一個模組,就意味著,前者的執行要依賴後者執行已完成,通常是為了資料準備妥當或保證所需服務已被註冊。

顯性的執行期依賴把之前啟動邏輯的“隱性依賴”完全暴露在陽光之下,改啟動邏輯不用提心吊膽。

圖14 - 依賴關係樹狀圖

configure()階段,該階段是根據之前的依賴樹遍歷執行。通常用於初始化一些資料配置、註冊IService服務、向前面依賴的模組註冊一些回撥等等。

此外這個階段還有額外的作用是插入BootTask,用於後面execute()階段的執行。

圖15 - configure階段

execute()階段,為了改變啟動流程不清楚的情況,強調啟動邏輯之間的依賴關係,我們現在將每個要執行的啟動步驟封裝為BootTask。前面的configure階段時,我們可以將BootTask插入到通過dependency()得到的依賴樹。每個Plugin同時也是一個BootTask,也因此擁有execute()介面。最終得到了包含所有BootTask的啟動樹,將遍歷執行所有節點執行execute()。

圖16 - BootTask

獨立使用BootTask的方式並不十分常見,通常Plugin本身的execute已經夠用。不過在一些通用型元件初始化嘗試會需要用到,如某些給某個全域性使用的預載入資源提前初始化的邏輯。

為何設計configure()和execute(),這可以理解為“收集任務”和“執行任務”的兩個階段。另外這樣的抽象還可以實現從外部排程execute的執行執行緒,將啟動邏輯和啟動非同步程式碼分開。順便解決了,原來非同步啟動程式碼混亂不堪的情況。

約束程式碼邊界

從之前的經驗看,要想約束好程式碼的邊界不被破壞,編譯上的隔離是唯一法寶。

除了工程和工程之間的分割,在工程的內部如果也能實現約束程式碼就更好了,算是將問題扼殺在搖籃裡。之前通常的做法是以module工程為單位的相互分離,但在工程內部並不限制程式碼相互引用。所以為了規範程式碼,常能看到用包名作為約定,區分內部功能職責,靠約定維持解耦。隨著時間推移,很快就能發現包名約定作為約束太弱了,在快速迭代的程式碼上很難一直維持下去。不管怎麼樣解決,總要通過一些手段審查程式碼引用的對不對。感覺有點防不勝防。為此,我們實現了一種簡單易用、粒度更細的工程組織結構——pins工程結構

圖17 - pins工程示例圖

使用pins工程結構,我們考慮的是要能對接相容以前結構下那麼多偽工程,又能在開始設計某個業務模組時,能用極小粒度的程式碼解耦,限制錯誤引用,以防後患。

pins工程能在module之內再次構建完整的多子工程結構,通過project.properties來指定編譯依賴關係。通過依賴關係在編譯時找到所有的資源和原始碼路徑。而對邊界的約束需要配合code-check工具在編譯期進行檢查,杜絕依賴關係之外的程式碼引用。

圖18 - code-check

這樣的工程組織形式的兩個明顯好處:

  • 約束程式碼粒度和小程式碼邊界的利器    

粒度極小,一個pin工程也許只有一個原始檔,只要它能表達一個獨立職責。對於任何一個模組,從內部約束自己的功能結構,是對整體程式碼邊界約束的極大補充。以前面插的結構為例,一個gallery業務可能提供了幾種不同的產品功能,以及支撐能力。那麼將其相互獨立的程式碼進行區分,避免混雜,就會顯得十分必要。清晰的結構,意味著後期維護成本的降低和開發效率的提高,留下了靈活性。

  • 避免的超量module的建立,輕量

pins工程某種程度上能減少一些粒度太小的module工程,也一定程度的緩解太多module工程時的gradle編譯效能問題。

至此,我們基本完成了重塑模組化的設計目標,解決掉很多之前沒有考慮的問題。算是模組化的加強版。另外設計是一方面,拆分解耦原來程式碼以及遷移還是另一回事,這個過程也是十分艱難和枯燥這裡就不細講了。接下來想辦法看看重構的效果。

看看效果

重新設計的模組化加上程式碼的重構。我們終於能滿足之前硬體同學的需要。同時一併解決許多拖欠的問題。

在編譯上,整體編譯速度會因為module增多而下降一些。但拆分module之後,卻能顯著加快單工程增量編譯的速度。和之前相比,一行程式碼的增量編譯耗時能減少60%。

除了滿足需求外,架構設計的效果並不好量化,不過我們嘗試用一個demo來說明。

WeChat nano

基於前面介紹過的輕量的微信核心mmkernel層,再配合一個不包含介面的基礎聊天模組和Auth模組,可以在短時間裡開發出一個及精簡版本的微信——WeChat nano。

圖19 - WeChat nano

模擬這個console的介面是單獨開發的,時間的大頭都花在這上面。

它的效果不錯:

  • 可以讓安裝包大小縮減到3.5M,大概是完整版本的10%

  • 能大幅減小記憶體佔用,約佔用完整版本的25% (注:只計算應用相關有不同的部分PSS) 

大概就是這樣。

取捨和選擇

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

我們參考了很多業界開放和發表的架構設計。總的來說,目前Android端App整體架設計上,除了聚焦在“大前端”之外,基本上都在“外掛化/應用沙盒”上面下功夫。可以參考如atlas、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工具

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

最後

重構整體架構不是一件容易事,通常也不太可能讓整個團隊停下來只做重構。所以一直以來微信的重構都是隨著版本迭代進行“拆分”-> “灰度” -> “迴流”的迴圈節奏。

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

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

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