微信Android模塊化架構(gòu)重構(gòu)實踐_第1頁
微信Android模塊化架構(gòu)重構(gòu)實踐_第2頁
微信Android模塊化架構(gòu)重構(gòu)實踐_第3頁
微信Android模塊化架構(gòu)重構(gòu)實踐_第4頁
微信Android模塊化架構(gòu)重構(gòu)實踐_第5頁
已閱讀5頁,還剩22頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、 微信Android模塊化架構(gòu)重構(gòu)實踐微信 Android 架構(gòu)歷史 微信 Android 誕生之初,用的是常見的分層結(jié)構(gòu)設(shè)計。這種架構(gòu)簡單、清晰并一直沿襲至今。這是微信架構(gòu)的 v1.x 時代。圖 1- 架構(gòu)演進(jìn)到了微信架構(gòu)的 v2.x 時代,隨著業(yè)務(wù)的快速發(fā)展,消息通知不及時和 Android 2.3 版本之前 webview 內(nèi)存泄露問題開始突顯。由于代碼、內(nèi)存、apk 大小都在增長,對系統(tǒng)資源的占用越來越多,導(dǎo)致微信進(jìn)程容易被系統(tǒng)回收。因此微信開始轉(zhuǎn)向多進(jìn)程架構(gòu),獨(dú)立的通信進(jìn)程保持長連接的穩(wěn)定性,獨(dú)立的 webview 進(jìn)程也阻隔了內(nèi)存泄露導(dǎo)致的問題。時間繼續(xù)推進(jìn),我們也遇到了 6553

2、5 問題和 LinearAlloc 問題。這時的微信已經(jīng)具備了許多功能像朋友圈、搖一搖、附近的人等等,分離核心功能和其他業(yè)務(wù)模塊變得越發(fā)重要。為此,微信開啟了第三次架構(gòu)改造 (v3.x)。我們對各種產(chǎn)品功能進(jìn)行解耦并拆分到相互獨(dú)立的 p_xxx 工程中,這是微信第一次進(jìn)行模塊化架構(gòu)的重構(gòu)。經(jīng)過幾個月的努力,微信拆出了幾十個 p 工程,它們都通過基礎(chǔ)組件訪問網(wǎng)絡(luò)、存儲等服務(wù),互相獨(dú)立并行。新的 p 工程架構(gòu)支撐了微信更快速的業(yè)務(wù)發(fā)展,配合多分支開發(fā)模式的改進(jìn),能夠支持團(tuán)隊多分支多 team 的并行開發(fā)。圖 2 - 架構(gòu)圖 為何再次重構(gòu)微信 原本好好的架構(gòu)出了什么問題? 從上個架構(gòu)之后的兩年多時間

3、里,微信 Android 基本沒有大的架構(gòu)改動。配合 gradle 的編譯,以及 git 的多分支并行開發(fā),微信的模塊工程數(shù)量不斷增多,支撐了游戲、支付等大功能,可以說這段時間里原有架構(gòu)起到了很好的作用。然而隨著代碼繼續(xù)膨脹,一些問題開始突顯出來。首先出問題的是基礎(chǔ)工程 libnetscene 和 libplugin?;A(chǔ)工程一直處于不斷膨脹的狀態(tài),同時主工程也在不斷變大。同時基礎(chǔ)工程存在中心化問題,許多業(yè)務(wù) Storage 類被附著在一個核心類上面,久而久之這個類已經(jīng)沒法看了。此外當(dāng)初為了平滑切換到 gradle 避免結(jié)構(gòu)變化太大以及太多 module,我們將所有工程都對接到一個 modul

4、e 上。缺少了編譯上的隔離,模塊間的代碼邊界出現(xiàn)一些劣化。雖然緊接著開發(fā)了工具來限制模塊間的錯誤依賴,但這段時間里的影響已經(jīng)產(chǎn)生。在上面各種問題之下,許多模塊已經(jīng)稱不上“獨(dú)立”了。所以當(dāng)我們重新審視代碼架構(gòu)時,以前良好模塊化的架構(gòu)設(shè)計已經(jīng)逐漸變了樣。圖 3 - 架構(gòu)逐漸的變化“君有疾在腠理,不治將恐深”,在我們還在猶豫到底要不要重構(gòu)的時候,硬件同學(xué)向我們提出了需求。希望將微信 Android 代碼移植到類似微信相冊這樣產(chǎn)品中。這樣就可以快速跟進(jìn)微信業(yè)務(wù)最新的支撐組件、協(xié)議、安全性、后臺服務(wù)等能力,而且代碼要盡可能精簡,可以選擇和定制模塊,可以移植模塊來實現(xiàn)原型嘗試。但就之前的情況來說,微信一時

5、難以滿足。這下定了,還得重構(gòu)。于是我們回過頭仔細(xì)看之前的設(shè)計,找找問題究竟是怎么來的。 問題出在哪 先尋找代碼膨脹的原因。翻開基礎(chǔ)工程的代碼,我們看到除了符合設(shè)計初衷的存儲、網(wǎng)絡(luò)等支持組件外,還有相當(dāng)多的業(yè)務(wù)相關(guān)代碼。這些代碼是膨脹的來源。但代碼怎么來的,非要放這?一切不合理皆有背后的邏輯。在之前的架構(gòu)中,我們大量適用 Event 事件總線作為模塊間通信的方式,也基本是唯一的方式。使用 Event 作為通信的媒介,自然要有定義它的地方,好讓模塊之間都能知道 Event 結(jié)構(gòu)是怎樣的。這時候基礎(chǔ)工程好像就成了存放 Event 的唯一選擇Event 定義被放在基礎(chǔ)工程中;接著,遇到某個模塊 A 想

6、使用模塊 B 的數(shù)據(jù)結(jié)構(gòu)類,怎么辦?把類下沉到基礎(chǔ)工程;遇到模塊 A 想用模塊 B 的某個接口返回個數(shù)據(jù),Event 好像不太適合?那就把代碼下沉到基礎(chǔ)工程吧就這樣越來越多的代碼很“自然的”被下沉到基礎(chǔ)工程中。我們再看看主工程,它膨脹的原因不一樣。分析一下基本能確定的是,首先作為主干業(yè)務(wù)一直還有需求在開發(fā),膨脹在所難免,缺少適當(dāng)?shù)膬?nèi)部重構(gòu)但暫時不是問題的核心。另一部分原因,則是因為模塊的生命周期設(shè)計好像已經(jīng)不滿足使用需要。之前的模塊生命周期是從“Account 初始化”到“Account 已注銷”,所以可以看出在這時機(jī)之外肯定還有邏輯。放在以前這不是個大問題,剛啟動還不等“Account 初始

7、化”就要執(zhí)行的邏輯哪有那么多。而現(xiàn)在不一樣,再簡單的邏輯堆積起來也會變復(fù)雜。此時,在模塊生命周期外的邏輯基本上只能放主工程。此外的問題,模塊邊界破壞、基礎(chǔ)工程中心化,都是代碼持續(xù)劣化的幫兇。總之在模塊化上我們忽視了一些重要的問題,必須重塑。 重塑模塊化 重塑模塊化,我們分解為三個目標(biāo):改變通信方式重新設(shè)計模塊約束代碼邊界 改變通信方式 前面講過,我們使用 Event 總線作為模塊間通信的媒介,這種設(shè)計很常見。然而當(dāng)回顧整體代碼時能發(fā)現(xiàn),Event 并非所有通信需要的最佳形式。它的特點適合一對多的廣播場景,依賴關(guān)系弱。一旦遇到需要一組業(yè)務(wù)接口時,用 Event 寫起來那是十分痛苦的。也正因如此,

8、這種情況下大家都跳過了 Event 的使用,直接將代碼下沉到了基礎(chǔ)工程,共享代碼,進(jìn)而導(dǎo)致基礎(chǔ)工程的不斷膨脹。所以選個合適的通信方式很有必要,我們希望兼顧考慮開發(fā)的便利性和協(xié)議的約束性。Event 不合適。協(xié)議通信如何?我們理解的協(xié)議通信,是指跨平臺 / 序列化的通信方式,類似終端和服務(wù)器間的通信或 restful 這種。現(xiàn)在這種形式在終端內(nèi)很常見了。協(xié)議通信具備一種很強(qiáng)力解耦能力,但也有不可忽視的代價。無論什么形式的通信,所有的協(xié)議定義需要讓通訊兩方都能獲知。通常為了方便會在某個公共區(qū)域存放所有協(xié)議的定義,這情況和 Event 引發(fā)的問題有點像。另外,協(xié)議如果變化了,兩端怎么同步就變得有點復(fù)

9、雜,至少要配合一些框架來實現(xiàn)。在一個應(yīng)用內(nèi),這樣會不會有點復(fù)雜?用起來好像也不那么方便?更何況它究竟解決多少問題呢。所以我們想要簡單點。經(jīng)過權(quán)衡,我們決定用模塊提供“SDK”的方式作為它與其他模塊進(jìn)行通信的手段。通?!癝DK”提供的是什么,是接口 + 數(shù)據(jù)結(jié)構(gòu)。這種方式好處明顯:實現(xiàn)簡單也能解決問題,IDE 容易補(bǔ)全、調(diào)用接口方便,不用配合工具,協(xié)議變化直接反映在編譯上,維護(hù)接口也簡單了。其實想想,用協(xié)議的方式在終端內(nèi)作為通信手段,開發(fā)效率低,也容易出錯。因此可能會誕生各種框架和工具來提升這里損失的效率。到頭來,是不是大家都實現(xiàn)了一套類似 RPC 這樣的封裝。其實本地的通信,能用接口就挺好,不

10、能用的時候,再用協(xié)議封裝也來得及。確定了方案,實現(xiàn)起來就很簡單。我們的注冊方式和接口訪問都很簡單。用接口注冊,再用接口訪問,不暴露實現(xiàn)細(xì)節(jié)。如下圖。圖 4 - 注冊接口圖 5 - 訪問接口 接下來,怎么暴露接口更方便? 模塊暴露“SDK”的方式無非就是新建個“SDK”工程,剝離接口和數(shù)據(jù)結(jié)構(gòu)到該工程里面,然后讓其他模塊引用編譯。但這樣有點麻煩,能不能再方便點?當(dāng)然有辦法。我們實現(xiàn)了另一種接口暴露的形式“.api 化”。使用方式和思路都很簡單。對于 java 文件,將工程里想要暴露出去的接口類后綴名從“.java”改成“.api”,就可以了。而且并不只是 java 文件,其他文件如果也想暴露,在

11、文件名后增加.api”,也一樣可以。圖 6 - “.api 化”當(dāng)然,要讓工程支持這樣的方式,gradle 文件肯定會有一點改變。settings.gradlebuild.gradle圖 7 - settings.gradle & build.gradle就這樣,可以說暴露接口變得非常容易,不用擔(dān)心實現(xiàn)類也被人引用到。而它的實現(xiàn)原理也相當(dāng)簡單:自動生成一個“SDK”工程,拷貝.api 后綴文件到工程中就行了,后面其他工程依賴編譯的只是這個生成的工程。簡單好用。還有個細(xì)節(jié),如果想編輯.api 后綴的 java 文件,為了能讓 Android Studio 繼續(xù)高亮該怎么辦?可以在 File Ty

12、pe 中把.api 作為 java 文件類型。圖 8 - 設(shè)置 File Types 重新設(shè)計模塊 要把模塊重新設(shè)計,還要做好幾件事。首先,消滅代碼經(jīng)常下沉的“三不管區(qū)域”基礎(chǔ)工程。這意味著原來的模塊要把之前下沉的代碼重新認(rèn)領(lǐng)回去。圖 9 - 分層結(jié)構(gòu)改造為了鞏固替代基礎(chǔ)工程的 mmkernel 層,不被濫用為新的代碼堆放處,順便還要解決中心化問題。就必須強(qiáng)化它的職責(zé)和設(shè)計。mmkernel 結(jié)構(gòu)可以很通用的定義為 CoreAccount/CoreNetwork/CoreStorage 三個部分,分別提供了核心賬號狀態(tài)(初始化、注銷)、網(wǎng)絡(luò)狀態(tài)回調(diào)(鏈接建立)、存儲狀態(tài)生命周期(db 創(chuàng)建、銷

13、毀、用戶存儲路徑切換、sdcard 掛起)。圖 10再然后是生命周期問題,我們需要重新設(shè)計正確的生命周期。之前講過,我們的模塊生命周期大體上只有“Account 初始化”和“Account 注銷”兩個階段。這已經(jīng)不夠用了。所以擴(kuò)大模塊的生命周期,就給了模塊實現(xiàn)各種代碼需要的時機(jī),才能避免大家往主工程塞代碼。圖 11實現(xiàn)新的生命周期是一個正確的選擇,同時產(chǎn)生了解決另一個問題的機(jī)會復(fù)雜的啟動流程。要知道主工程的代碼一部分原因是啟動流程堆積造成的,邏輯多了代碼自然多。隨之而來的問題就是代碼多了,邏輯也就跟著復(fù)雜起來。微信的初始化邏輯是順序排列在一起并從上到下執(zhí)行,某種情況下還會異步啟動。當(dāng)程序啟動流

14、程比較復(fù)雜時,這樣的代碼會產(chǎn)生“隱性依賴”的問題?!半[性依賴”顧名思義就是:原本并應(yīng)該存在依賴的代碼,隨著版本的迭代逐漸產(chǎn)生了依賴,而且還不明顯。這樣的情況會讓情況惡化,大家只敢往里面堆代碼,但卻不敢“亂動”。所以重新設(shè)計的模塊應(yīng)該要徹底避免這些問題。我們重新定義了模塊的生命周期,將模塊的生命周期延長到應(yīng)用啟動和退出。而后,每個模塊都可以定義一個 Plugin 類,作為模塊的“支柱”或“起點”。作為解決初始化問題的手段,它具備幾個主要階段:dependency()、configure()、execute()圖 12 - plugin 初始化的幾個階段dependency() 階段,用于設(shè)置需要

15、依賴的其他 Plugin,當(dāng)然提供那個 Plugin 的別名接口類就可以了。圖 13 - 設(shè)置 dependency依賴階段我們會生成整個模塊的依賴樹。這與編譯時的依賴不同。通常的依賴關(guān)系是分為兩種的,一種是類型依賴也就是編譯期依賴,需要被依賴模塊提供具體類型才能編譯通過;另一種依賴則是運(yùn)行期的邏輯依賴或數(shù)據(jù)一致性依賴,當(dāng)一個模塊用這種方式依賴另一個模塊,就意味著,前者的執(zhí)行要依賴后者執(zhí)行已完成,通常是為了數(shù)據(jù)準(zhǔn)備妥當(dāng)或保證所需服務(wù)已被注冊。顯性的運(yùn)行期依賴把之前啟動邏輯的“隱性依賴”完全暴露在陽光之下,改啟動邏輯不用提心吊膽。圖 14 - 依賴關(guān)系樹狀圖configure() 階段,該階段是

16、根據(jù)之前的依賴樹遍歷執(zhí)行。通常用于初始化一些數(shù)據(jù)配置、注冊 IService 服務(wù)、向前面依賴的模塊注冊一些回調(diào)等等。此外這個階段還有額外的作用是插入 BootTask,用于后面 execute() 階段的執(zhí)行。圖 15 - configure 階段execute() 階段,為了改變啟動流程不清楚的情況,強(qiáng)調(diào)啟動邏輯之間的依賴關(guān)系,我們現(xiàn)在將每個要執(zhí)行的啟動步驟封裝為 BootTask。前面的 configure 階段時,我們可以將 BootTask 插入到通過 dependency() 得到的依賴樹。每個 Plugin 同時也是一個 BootTask,也因此擁有 execute() 接口。最

17、終得到了包含所有 BootTask 的啟動樹,將遍歷執(zhí)行所有節(jié)點執(zhí)行 execute()。圖 16 - BootTask獨(dú)立使用 BootTask 的方式并不十分常見,通常 Plugin 本身的 execute 已經(jīng)夠用。不過在一些通用型組件初始化嘗試會需要用到,如某些給某個全局使用的預(yù)加載資源提前初始化的邏輯。為何設(shè)計 configure() 和 execute(),這可以理解為“收集任務(wù)”和“執(zhí)行任務(wù)”的兩個階段。另外這樣的抽象還可以實現(xiàn)從外部調(diào)度 execute 的執(zhí)行線程,將啟動邏輯和啟動異步代碼分開。順便解決了,原來異步啟動代碼混亂不堪的情況。 約束代碼邊界 從之前的經(jīng)驗看,要想約束

18、好代碼的邊界不被破壞,編譯上的隔離是唯一法寶。除了工程和工程之間的分割,在工程的內(nèi)部如果也能實現(xiàn)約束代碼就更好了,算是將問題扼殺在搖籃里。之前通常的做法是以 module 工程為單位的相互分離,但在工程內(nèi)部并不限制代碼相互引用。所以為了規(guī)范代碼,常能看到用包名作為約定,區(qū)分內(nèi)部功能職責(zé),靠約定維持解耦。隨著時間推移,很快就能發(fā)現(xiàn)包名約定作為約束太弱了,在快速迭代的代碼上很難一直維持下去。不管怎么樣解決,總要通過一些手段審查代碼引用的對不對。感覺有點防不勝防。為此,我們實現(xiàn)了一種簡單易用、粒度更細(xì)的工程組織結(jié)構(gòu)pins 工程結(jié)構(gòu)圖 17 - pins 工程示例圖使用 pins 工程結(jié)構(gòu),我們考慮

19、的是要能對接兼容以前結(jié)構(gòu)下那么多偽工程,又能在開始設(shè)計某個業(yè)務(wù)模塊時,能用極小粒度的代碼解耦,限制錯誤引用,以防后患。pins 工程能在 module 之內(nèi)再次構(gòu)建完整的多子工程結(jié)構(gòu),通過 perties 來指定編譯依賴關(guān)系。通過依賴關(guān)系在編譯時找到所有的資源和源碼路徑。而對邊界的約束需要配合 code-check 工具在編譯期進(jìn)行檢查,杜絕依賴關(guān)系之外的代碼引用。圖 18 - code-check這樣的工程組織形式的兩個明顯好處:約束代碼粒度和小代碼邊界的利器粒度極小,一個 pin 工程也許只有一個源文件,只要它能表達(dá)一個獨(dú)立職責(zé)。對于任何一個模塊,從內(nèi)部約束自己的功能

20、結(jié)構(gòu),是對整體代碼邊界約束的極大補(bǔ)充。以前面插的結(jié)構(gòu)為例,一個 gallery 業(yè)務(wù)可能提供了幾種不同的產(chǎn)品功能,以及支撐能力。那么將其相互獨(dú)立的代碼進(jìn)行區(qū)分,避免混雜,就會顯得十分必要。清晰的結(jié)構(gòu),意味著后期維護(hù)成本的降低和開發(fā)效率的提高,留下了靈活性。避免的超量 module 的創(chuàng)建,輕量pins 工程某種程度上能減少一些粒度太小的 module 工程,也一定程度的緩解太多 module 工程時的 gradle 編譯性能問題。至此,我們基本完成了重塑模塊化的設(shè)計目標(biāo),解決掉很多之前沒有考慮的問題。算是模塊化的加強(qiáng)版。另外設(shè)計是一方面,拆分解耦原來代碼以及遷移還是另一回事,這個過程也是十分艱

21、難和枯燥這里就不細(xì)講了。接下來想辦法看看重構(gòu)的效果。 看看效果 重新設(shè)計的模塊化加上代碼的重構(gòu)。我們終于能滿足之前硬件同學(xué)的需要。同時一并解決許多拖欠的問題。在編譯上,整體編譯速度會因為 module 增多而下降一些。但拆分 module 之后,卻能顯著加快單工程增量編譯的速度。和之前相比,一行代碼的增量編譯耗時能減少 60%。除了滿足需求外,架構(gòu)設(shè)計的效果并不好量化,不過我們嘗試用一個 demo 來說明。WeChat nano基于前面介紹過的輕量的微信內(nèi)核 mmkernel 層,再配合一個不包含界面的基礎(chǔ)聊天模塊和 Auth 模塊,可以在短時間里開發(fā)出一個及精簡版本的微信WeChat nan

22、o。圖 19 - WeChat nano模擬這個 console 的界面是單獨(dú)開發(fā)的,時間的大頭都花在這上面。它的效果不錯:可以讓安裝包大小縮減到 3.5M,大概是完整版本的10%能大幅減小內(nèi)存占用,約占用完整版本的 25% (注:只計算應(yīng)用相關(guān)有不同的部分 PSS)大概就是這樣。 取舍和選擇 對于架構(gòu)重構(gòu),我們也曾放眼行業(yè)內(nèi)已經(jīng)發(fā)布過的各種方案,希望從中找到一些解決思路。我們參考了很多業(yè)界開放和發(fā)表的架構(gòu)設(shè)計??偟膩碚f,目前 Android 端 App 整體架設(shè)計上,除了聚焦在“大前端”之外,基本上都在“插件化 / 應(yīng)用沙盒”上面下功夫??梢詤⒖既?atlas、small、DroidPlug

23、in、DynamicApk 等等方案,不難發(fā)現(xiàn)讓模塊最終具備動態(tài)性是它們最核心的能力。“大前端”是個熱門的方向。我們結(jié)合自身情況,考慮到遷移已有代碼不是一件容易事,讓團(tuán)隊徹底切換編程工具短期不現(xiàn)實。這種“遠(yuǎn)水解不了近渴”的感覺,也只能暫且作罷。我們把目光投向了“插件化 / 應(yīng)用沙盒”。沙盒的好處是它具備很強(qiáng)的隔離性,有些方案可以徹底隔離代碼和資源,邊界限制性極強(qiáng),效果很好。此外,多數(shù)方案也都具備動態(tài)更新能力、補(bǔ)丁能力,動態(tài)性基本成為標(biāo)配。所以這時我們在考慮需不需要走上這樣的路。一切從自身情況出發(fā)。在微信的角度看,我們最關(guān)心的問題是如何能約束住代碼邊界,如何防止架構(gòu)劣化,如何提高開發(fā)效率。這樣的

24、情況下,重新審視了具備動態(tài)性的插件化和沙盒方案。先從動態(tài)性上考慮,回看業(yè)內(nèi)的使用經(jīng)驗,目前的動態(tài)性通常有兩個主要的用途:1)作為熱補(bǔ)丁使用;2)業(yè)務(wù)并行發(fā)布需求強(qiáng)烈,例如運(yùn)營需求,作為快速發(fā)布的手段。對于第二點,就目前的微信團(tuán)隊來說是較少遇到的弱需求。相較之下,工具類和電商類應(yīng)用常有符合的使用場景。而對于第一點,我們則期望在實現(xiàn)模塊化上,目標(biāo)相對純粹一些,專心解決代碼自身問題,之后再通過 tinker 在開發(fā)階段之外實現(xiàn)熱補(bǔ)丁上的動態(tài)性需要。再來看隔離性。需要說明的是,這里的隔離型主要來自于沙盒架構(gòu)效果。對于非獨(dú)立插件化工程(需要編譯依賴其他插件)除了動態(tài)性,它的作用和普通 module 在代

25、碼隔離上沒有區(qū)別,不做討論。我們心里很期盼代碼與代碼之間那種干凈清爽的分割效果,但事與愿違。圖 20 - 模塊依賴示意圖從上面這張模塊依賴示意圖看到,微信業(yè)務(wù)模塊之間數(shù)據(jù)關(guān)系相當(dāng)復(fù)雜,模塊間相互訪問數(shù)據(jù)、共享某些功能的行為如此普遍。而實際情況比示意圖更麻煩。面對微信業(yè)務(wù)數(shù)據(jù)相互間頻繁的調(diào)用,沙盒隔離容易導(dǎo)致代碼復(fù)用困難和相互調(diào)用麻煩,想在微信上實現(xiàn),目前困難極大。重構(gòu)難度不談,單是隔離的收益就很有可能無法彌補(bǔ)開發(fā)效率上的損失。從開發(fā)模式上看也是一樣。微信 Android 團(tuán)隊目前每個迭代大概三四十人參與,內(nèi)部溝通成本不算高到不可接受。通常開發(fā)同學(xué)可能要同時開發(fā)和修改幾個模塊并保證他們相互模塊獨(dú)

26、立,同時又可能有頻繁的模塊間通信。這種時候,模塊調(diào)用方不方便顯得很重要。此外還需要考慮的問題,從幾個成熟方案中都能看到 hook Android 框架、修改 aapt、替換或包裝 android gradle plugin、代理組件等等設(shè)計。這些方案的復(fù)雜度和兼容性代價,不能忽視。使用和維護(hù)它們需要仔細(xì)權(quán)衡。所以思前想后我們?nèi)赃x擇了重走最開始的模塊化之路。一切問題的根本還是在那個說的很多年的代碼邊界、解耦和內(nèi)聚上。只要有了清晰獨(dú)立的代碼模塊,才有將來其他變化的可能性。 代碼之外,架構(gòu)之內(nèi) 模塊負(fù)責(zé)人制度 有這樣一句話,“不被監(jiān)管的權(quán)利一定會發(fā)生腐敗” 。如果放到軟件開發(fā)的行當(dāng)來說,就是“不被監(jiān)

27、管的代碼也一定會發(fā)生劣化”。所以代碼應(yīng)該要接受“監(jiān)管”。為了能長期有效的保持代碼質(zhì)量,我們開始執(zhí)行新的代碼審查機(jī)制模塊負(fù)責(zé)人制度。代碼審查的好處毋庸置疑。在這之前,微信由于業(yè)務(wù)發(fā)展快速,同學(xué)們經(jīng)常會變換需求的開發(fā)方向。面對著業(yè)務(wù)模塊數(shù)量比人多的情況,開發(fā)同學(xué)經(jīng)常一個人需要開發(fā)多個模塊。也因此許多模塊被無數(shù)人維護(hù)過,基礎(chǔ)的支撐工程更是如此。這種類似游擊戰(zhàn)的方式,開發(fā)效率很高,支撐了微信快速的研發(fā)節(jié)奏,但也導(dǎo)致了“無主代碼”特別多。大家缺少對代碼的“歸屬感”,也降低了改進(jìn)優(yōu)化模塊的欲望。另外在這之前,代碼審查是由 leader 對申請回流主干的 Merge Request 進(jìn)行 review,這導(dǎo)

28、致效率較低且容易遺漏問題。合理的代碼審查更應(yīng)該是全員性質(zhì)的。模塊負(fù)責(zé)人制度嘗試改變這些現(xiàn)狀。通過大家認(rèn)領(lǐng)模塊,對模塊的代碼和設(shè)計負(fù)責(zé),對模塊對外提供的接口服務(wù)負(fù)責(zé),對其他人修改自己模塊的行為進(jìn)行監(jiān)督。這些情況明顯提高開發(fā)同學(xué)的代碼所有感,改變大家修改優(yōu)化和修改代碼的動機(jī)。推動模塊負(fù)責(zé)人制度,漸進(jìn)式的推動了大范圍的代碼審查,這樣的方式很適合像微信這樣沒有從一開始執(zhí)行全員性質(zhì) Code Review 的項目。目前模塊負(fù)責(zé)人機(jī)制運(yùn)轉(zhuǎn)順利,代碼審查率和模塊認(rèn)領(lǐng)率都在提高。 重構(gòu)與開發(fā)者心態(tài)的關(guān)系 在一個長期沒有改進(jìn)的框架下,開發(fā)者的習(xí)慣可能會逐步變成跟隨式、保守式的開發(fā)。這大概可以被描述成“只要別人這

29、樣做,我也這樣做,哪怕這么樣的設(shè)計不好,但也不會錯”。隨著心態(tài)逐漸普遍,另一種情形出現(xiàn):經(jīng)常能聽到有同學(xué)吐槽一些代碼,卻更少看到代碼在被改進(jìn)。這說明一些沉積的問題不是沒有被大家發(fā)現(xiàn),只是沒有人愿意去修改。這種情況下代碼和框架會隨著時間變得越來越差,有些問題逐漸變成“陳年舊病”。 面對這個問題首先要說,這不是開發(fā)者合格與否的問題,實際上有想法的開發(fā)人員有很多,但想將每個想法轉(zhuǎn)換成代碼并讓大家接受,并不是一件很容易的事。尤其在一個大框架下,嘗試改變的代價很大。如果他的主要任務(wù)不在改進(jìn)某些模塊上,那么很多想法最后都無法變成現(xiàn)實。這也是為什么保守和跟隨的習(xí)慣會逐漸變的普遍。保守的氣氛需要被打破。當(dāng)開啟

30、一次重構(gòu)之后,你會發(fā)現(xiàn)團(tuán)隊中會有很多積極的聲音響應(yīng),他們會把積壓的想法和意見拋出來。一次問題的解決,可能會為另一個問題的解決帶來機(jī)會,其他開發(fā)同學(xué)的一些想法也許就能更容易落地。所以不定期的推動一些模塊的重構(gòu),將一些對代碼的不滿釋放出來,是一種不錯的激活。此外在重構(gòu)之后,還要考慮引導(dǎo)開發(fā)的代碼組織方式切換,多用模板、正確的代碼實例等,讓他們可以放心參考。 模塊劃分經(jīng)驗談 維持代碼邊界代碼的邊界就像一堵墻,架構(gòu)的劣化都是從這堵墻的瓦解開始的。從以往的經(jīng)驗來看,編譯上的隔離是最好的約束手段,單純的約定或準(zhǔn)則并不能永遠(yuǎn)的保持下去。 所以在任何情況下都盡可能不要放開編譯上的約束。接著,將接口和實現(xiàn)分離,

31、其他工程只依賴接口而不依賴實現(xiàn),這樣的邊界效果更好。當(dāng)然破壞無處不在,例如遇到某個緊急需求要某模塊新增若干接口,就可能出現(xiàn)跳過接口直接依賴實現(xiàn)工程進(jìn)行開發(fā)的情況。這時可以考慮通過代碼的審查進(jìn)行監(jiān)督,也可以通過開發(fā)簡單的編譯腳本,檢查是否有不當(dāng)依賴產(chǎn)生。劃定模塊邊界的細(xì)節(jié)問題當(dāng)對代碼進(jìn)行解耦時,即便大體上的模塊職責(zé)劃分已經(jīng)清晰,但因為模塊間的各種業(yè)務(wù)關(guān)系,細(xì)節(jié)上仍會遇到糾纏不清的情況。事實上,因為需求及功能的不同,并沒有哪一種模塊劃分的規(guī)則是完全適用于每個應(yīng)用的。隨著業(yè)務(wù)的發(fā)展和變化,模塊邊界出現(xiàn)不合適的情況完全符合預(yù)期。那么如何讓模塊劃分更讓大家覺得合理,或者說當(dāng)遇到一個兩難選擇時,按照什么樣的方式大家會更好理解?我們建議的方法其實也很簡單:試著對代碼“講一個符合邏輯的故事”,哪個故事講得通,你就可以將之作為拆分的選擇。因為代碼解耦從來不是問題,糾結(jié)的只是解耦

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論