悅跑圈Android單業務開發,提高編譯效率15倍

關于標題

“15倍”是怎么算出來的吶?

配置:
Mac mini,雙核 Intel Core i5 2.6GHz,8G內存,SSD(自行更換)

性能:

全量編譯application平均需要5分鐘,單業務application編譯運行,平均僅需7~20秒. (300s/20s = 15)

(如果你用相同配置windows系統,基本是編譯不過去的,至少要i7。)

什么是單業務編譯

如果讀者你看過筆者寫的《App組件化與業務拆分那些事》《Android使用Provider做業務數據交互》,就能了解如何對工程劃分業務了,此處不累贅。

那兩篇文章,是為本文做鋪墊。前兩篇講了開發思路、理念方面,說了那么多優點,貌似也沒什么實質數據支撐。那么,本文讓你見識一下分業務開發的力量。

常見的開發模式,是一個工程,一個application、多個module或無module

單application,多module

這種模式不足夠支撐日益龐大的工程。于是,我們提出:

一個工程,多個application、多個module。

多application,多module

(由于排版問題,此圖省略了Base Library)

Main Application跟“單業務application”時的Application一樣,就是我們打包的application. 那么,application A、application B...又是什么呢?

從圖里面看出,Application A只依賴Module A,Application B只依賴Module B....每個application可獨立編譯、運行。

獨立編譯業務優勢

1.編譯快、單元測試快
2.簡化調試流程
3.更專注業務
4.可嵌入不需要在Main Application執行的代碼
5.隔離不必要的業務和組件

1.編譯快

正如前文提到,悅跑圈從全量編譯平均5分鐘,降到單業務編譯7~20秒。就這么一個優勢,就有充足的理由讓我們從單application改造成多application

單元測試快

說說單元測試吧,很多同學都沖著“單元測試快速反饋結果、能提高開發效率”。然而,自身工程臃腫不堪,跑一個單元測試gradle會全工程掃描(部分或全量編譯),時間上壓根談不上快(盡管比運行app再手動debug要好很多),有error還報錯。這種情況還是挺常見的,例如你在重構代碼,改幾個業務,必須所有業務代碼都寫好(至少沒有error),才能跑單元測試。

當項目按業務分成若干module,跑單元測試時,gradle僅掃描該業務module,即使其他module有報錯,也不影響本次單元測試。而且因為編譯代碼量少,單元測試就能跑快了。

我們目前跑一個junit單元測試,算上編譯和運行時間,僅需幾秒。如果用robolectric跑DAO測試,就比較慢了,這個跟robolectric框架機制有關。

2.簡化調試流程

業務比較多的APP,不一定每個功能的入口都很明顯,有可能依賴其他功能流程。單業務application運行很好地解決了這個問題。

例如,你要調試“查看某用戶勛章”,那么你必須先進入該用戶界面,再點擊“他的勛章”,才能進入“勛章墻”界面。你要找到用戶名叫“鍵盤男”(uid=4)的用戶,必須先去“發現用戶”界面,輸入“鍵盤男”或“4”,點擊搜索,從搜索結果列表,找到想要找的“鍵盤男”用戶,再點進去......光想想就覺得蛋疼,更別說做代碼調試。

發現用戶 -> 搜索“鍵盤男” -> 進入用戶詳情界面 -> 進入uid=4勛章墻

全流程

由于單業務application不會編譯進Main application,因此我們在單業務application做什么都可以。于是,我們可以在“勛章application”寫一個頁面,有個Button和EditText,EditText輸入uid,點Button直接進入該uid的勛章墻界面。是不是很簡單?

EditText輸入4 -> 進入uid=4勛章墻

單業務流程

同學們回憶一下,自己是不是把很多調試時間浪費在繁瑣的流程上?

3.更專注業務

這個話題在筆者前兩篇文章提及很多次了。一個業務一個module,能讓你更清晰地管理業務代碼和資源;現在,一個業務一個application,能讓你更清晰地梳理業務流程,單業務application僅依賴當前業務module,讓開發人員隔離無關業務代碼。

對于經驗尚淺的同學,這點可能稍微難理解。說實話,“專注業務”聽起來比較虛,但恰恰能無形地讓程序員提高開發效率。

例如,單業務開發把業務之間代碼隔離,一來不會讓你的業務代碼干擾其他開發人員,二來其他業務代碼也不會干擾你的代碼

再舉個反例,如果你在業務A寫了ImageUtils.getSize(),其實這個ImageUtils跟業務A相關,例如根據dpi獲取A界面某圖片顯示大小;在沒有分業務開發情況下,業務B也調用你的ImageUtils.getSize(),當業務A修改ImageUtils.getSize(),業務B就有bug了.....如果是分業務開發,業務A下的ImageUtils(應該命名為AImageUtils)不可能被業務B調用,那么開發業務B的同學,只能乖乖地自己寫一個BImageUtils.getSize()

CodeReview時,審核人要review業務A代碼,他只需要看A module下的代碼即可,不需要關心其他module代碼,那么review速度和質量,自然有所提高。

這些情況,在平常開發中經常碰到,分業務開發可以很大程度上避免上述低級錯誤。歸根到底,分業務就是高內聚,低耦合的編程理念:單業務高內聚,業務之間低耦合。

4.可嵌入不需要在Main Application執行的代碼

這主要針對調試時修改數據的場景。例如,調試“無本地緩存,重新請求數據”場景,如果每次調試都要清空整個app數據,很麻煩,而且可能引起流程上的問題。

單業務application為這種場景提供了解決方案:在單業務application某Activity,點擊某個Button,執行清空某數據的代碼。

調試場景:
1.斷網 -> 勛章墻 -> 顯示緩存勛章
2.斷網 -> 清空勛章數據 -> 無勛章,并顯示默認圖

清空緩存

如果沒有單業務application,怎么做?很可能在某個界面,寫一個Button,點擊清空數據勛章緩存。但這里有問題,Button是寫在勛章業務module,Main Application會依賴,在打包時需要去掉這部分代碼,或者加if(BuildConfig.DEBUG)等條件。筆者非常不支持這種會影響Main application的做法,有可能因為這個改動,引起不必要的bug。

單業務application還能實現很多場景,希望同學們能自己去發掘!

5.隔離不必要的業務和組件

關于“隔離業務”,上文已經提到了部分,這里補充一下更多場景。

隔離其他業務數據

悅跑圈Android 勛章業務 需要獲取 跑步業務 中個人跑量數據。按常理,為了實現這個需求,勛章業務 要依賴 跑步業務。我們的《Android使用Provider做業務數據交互》方案,正好解決依賴問題。

使用Provider可以讓勛章業務不需要依賴跑步業務,但問題來了,個人跑量數據誰提供?在單業務application中,我們可以聲明一個MockRunProvider,跟跑步業務RunProvider實現同樣的RunProtocol接口,并保持authority一致;或者修改服務中心路由配置,讓原來指向RunProviderrun authority,改成指向MockRunProvidermock authority。當然,MockRunProvider返回的數據不是真實的,是寫死的。

由于編譯運行單業務application很快,你可以快速地修改MockRunProvider數據來調試,這讓開發人員省去很多不必要的流程。例如,需求當跑量大于1000KM顯示文案X:正常開發流程,服務器上必須有某用戶信息跑量大于1000KM,跑步業務請求回本地,再調用RunProvider才返回個人跑量,又或者在debug模式動態修改變量;而單業務application+MockRunProvider只需要寫死跑量1000+KM就可以走后面的流程了,根本不需要關心服務器數據和請求數據,也不需要debug模式修改數據,開發人員只需要關心勛章業務如何實現即可!

隔離、替換組件

悅跑圈Android使用各種開源庫,在《國內Top500Android應用分析報告》就看到我們使用RxJava(logo還挺醒目的_)。不是每個業務都需要所有組件,那么單業務application,可以按需要引用開源庫(當然rxjava無處不在),原理跟按需依賴業務module一樣。

替換組件,目前我們還不需要做。之前去GMTC聽天貓團隊介紹,他們的日志組件、網絡組件等,代碼量都很大,而且做了很多事情,如果在業務開發時使用,一來加大編譯壓力,二來調試比較麻煩。他們業務、組件本身是接口隔離,在開發業務時,可以使用更簡單的日志、網絡組件,來替換笨重的原組件,可以縮短編譯時間,而且調試方便。這一點跟上文提到“業務隔離”思路如出一轍。


遇到困難

上述侃侃而談單業務開發那么多好處,事實上我們也是匍匐前進、連爬帶滾地一路走過來。要做到業務高度隔離,并不是一件容易的事:

1.對框架熟悉
2.基礎框架必須高度解耦
3.開發團隊要有足夠經驗

1.對框架熟悉

對框架熟悉是重中之重。不像很多年輕APP,悅跑圈從第一行代碼到今天,經歷了不少年頭,當年還是用httpClient啊!筆者有幸從悅跑圈第一行代碼待到現在(居然還未被老板炒魷魚)。項目早期框架,也是各種耦合,有不少無注釋隱晦的代碼,筆者還是挺熟悉,畢竟不少爛代碼出自筆者手,,這給后期重構帶來很大幫助。

開發團隊還會遇上人員變更的狀況,寫某個業務的程序員離職了,然后這個版本要對這個業務加需求。如果原業務代碼很隱晦,注釋又少,這就非常蛋疼了。筆者建議,如果原代碼真的非常難懂,直接重構吧!(記得做單元測試)

2.基礎框架必須高度解耦

原則上,基礎框架是不能跟任何業務耦合的。要做到這點相當不容易,例如請求接口時,需要帶上用戶信息,用戶信息需要依賴用戶業務,怎么辦?

目前的解決方案,把用戶信息作為靜態變量,用靜態方法獲取,如果獲取不到,讀取本地數據。因此,讀取用戶本地數據,作為基礎框架一部分。用戶業務還有很多功能,請求用戶信息、用戶界面等,這些跟基礎框架是解耦的。

還有組件,日志、網絡等組件,這些是不是應該互相解耦?目前我們還是有相互依賴,一并放在基礎框架,日后我們會改善這一塊。

總而言之,基礎框架越輕量越好,保證必要的功能,不是經常使用的組件、業務,讓單業務application分別依賴即可。

3.開發團隊經驗

不怕神一樣的對手,只怕豬一樣的隊友。

這年頭,要找靠譜的程序員,十分不容易。遇到水平低的小伙伴,王者農藥團滅多痛苦就不用提了。幸好筆者領導挑人的水平,還是不錯的。

有靠譜的團隊,是框架改進的重要因素。團隊在技術上一拍即合的概率,不比遇到一見鐘情的伴侶高。如果萍水相逢的隊友,在技術上有分歧是正常的,如果由于技術前瞻性不足,對前沿技術抱著抵觸心理,麻煩就大了。之前有位2015年末離職的同事,工作經驗比筆者要久,他在使用rxjava上存在分歧,認為需要更長時間觀察rxjava。經過一番討論,他選擇離職(當然并不主要是rxjava的原因,更多的是對團隊氛圍的不適應)。現在,做Android的同學誰不知道rxjava?

本文所說的分業務開發、單業務application,基礎框架解耦,實現起來并不簡單,在筆者剛提出這種方案時,也不是所有隊友都同意。有隊友認為這樣做太麻煩,還不如用Freeline來得直接。不過筆者不習慣使用Freeline,因為使用時遇到不少坑,還是老老實實把代碼分業務,減少單次編譯量這種做法才能從根本上解決編譯慢的問題。目前Android項目是允許Freeline存在,畢竟分業務開發和Freeline是不沖突的,多嘗試前沿技術,對團隊來說好處多余壞處。

如果你有改進框架的想法,在靠譜的團隊里面,遇到的阻力會少很多。當然你提出的方案并不一定合適,至少大家會一起討論可行方案,給你更多意見。

好的框架,一定是最適合你的團隊,而不一定是最先進的;
靠譜的團隊,在框架改進遇到坑時,能及時提出適當的解決方案。


小技巧

多個業務流程聯調

筆者一直強調單業務application開發,其實可以不止單業務,可以是多業務application。當需要幾個業務聯調(功能測試),多業務application的價值就體現出來了。

例如,報名“廣州線上馬拉松”,立即獲得“廣州線上馬勛章”,并需要彈窗提示。這里涉及兩個業務:線上馬業務勛章業務,線上馬拉松報名頁是 線上馬業務,勛章彈窗是勛章業務。這時一個application依賴 線上馬module勛章module,線上馬報名完畢后,調用 勛章provider,請求后端數據,執行彈窗邏輯。

當需要多個業務聯調,application可以依賴多個業務,但必須保證業務module之間是解耦的。

反向調用Main Application代碼

在重構過程,不時會遇到某些代碼互相耦合,一改就要改一大片。在有限的開發時間里,這樣做是很危險的,因為你不知道遺留代碼牽連到多少舊代碼,太大的改動很可能引起各種bug不說,緊張的開發周期不允許你這么做。

本文不斷提到provider做業務之間數據交互,不僅僅是平行的業務module之間能通過provider互相調用,業務module也可以用provider反向調用Main Application的代碼。因此,遇到相互耦合代碼,可以把已明確的某業務的代碼放到該業務module,依賴交給provider去解耦。


不足

代碼合并

我們開發使用git-flow工作流程,我相信很多同學也這么做。由于單業務application開發模式,也是把所有業務(不是所有組件)放在一個工程下,所以,單一工程git-flow合并代碼時,同樣有單一工程的詬病:所有代碼都有可能merge和沖突

merge和解決沖突是合并時必不可少的環節,為什么筆者說成詬病?筆者強調的是“所有代碼都有可能”,并不是指merge和沖突。

開發新功能到merge時,最怕就是別人改了的代碼你也改了,需要解決沖突。單一工程merge分支時,對于代碼審核人員來說,所有代碼都可能被改動。當他Review時就非常蛋疼了,要不仔細查看每次commit的代碼,要不只挑重要代碼看,要不不審直接merge。當大量代碼merge,審核人員根本沒時間看每次commit的代碼,基本只能先merge,再運行或者單元測試看有沒問題。這樣很容易隱藏的bug,只能相信測試工程師了.....

如果每個業務是一個工程,業務發布到maven倉庫,主工程和業務之間通過gradle依賴,merge時就放心多了。因此一個業務一個工程,merge時只針對當前工程,因此代碼有什么改動,審核人員心里有數,即使有bug也是該業務的bug,不會影響其他業務。

git-flow工作流程


總結

業務解耦手段有很多,本文提及的“provider解耦”僅僅是筆者習慣做法,對于文中描述需要解耦的地方,可以使用其他解耦方式。

一個工程多application并不是最好的開發方式,它不適合業務非常龐大的APP,例如支付寶、天貓、攜程、鏈家等,超級APP必然是多project玩耍;也不適合業務量很少的APP,僅僅適合當前悅跑圈Android團隊。

我們不會止步于現狀,未來會做業務持續集成,有可能單個業務為一個工程,分多個工程開發(目前部分組件為單獨工程,主工程通過maven倉庫依賴)。

我相信有不少讀者經驗比我們要豐富,如果你有其他觀點或疑問,歡迎吐槽本文。

(本文所有觀點,僅代表筆者個人,不代表悅跑圈開發團隊)

demo:https://coding.net/u/kkmike999/p/MultiApplication/git


關于作者

我是鍵盤男。
在廣州生活,悅跑圈Android工程師,猥瑣文藝碼農。每天謀劃砍死產品經理。喜歡科學、歷史,玩玩投資,偶爾旅行。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容