1. 程式人生 > >App 模組化實戰經驗總結

App 模組化實戰經驗總結



隨著業務的不斷髮展壯大,App 端所承擔的功能也越來越重,特別是程式碼幾易其主之後開始變得雜亂無章,牽一髮而動全域性的事情時常發生。為了應對團隊壯大之後的開發模式,我們必須要對業務進行隔離,同時沉澱出通用元件,完善移動開發的基礎設施。

1. 痛點

模組化之前,我們主要面臨以下痛點:

  • 業務邊界不清晰

  • 通用程式碼與業務程式碼耦合

  • 程式碼、資原始檔大量重複

  • 常量滿天飛

2.png

其中業務邊界不清晰是最大的痛點,最直接的表現就是處處有雷,經常會引入新的 Bug,而且很多 Bug 往往不能從根本上解決,程式碼維護成本居高不下。

2. 重構原則

模組化並不能一蹴而就,我們在重構的同時也在做新需求,每次看到那一坨舊程式碼心中就會有無數只”草泥馬”奔騰而過,乾脆重寫的無奈之情難以抑制,結果在紅牛的日夜陪伴下寫出來的新程式碼雖然看上去“漂亮”,但是實際上問題更多,得不償失。吃過幾次苦頭之後,我們總結出了重構的三項基本原則:

3.png

2.1 漸進式重構

如果一段程式碼已經比較穩定,可以從中抽取一部分功能重寫,不要一上來就全部推翻重寫,可以慢慢淘汰掉老程式碼。

2.2 iOS / Android 互相參考

業務程式碼總是驚人的相似,兩端互相參考的過程中,不但可以 Review 程式碼,還能加深對業務的理解,可謂一舉兩得。
實踐證明,如果人手緊張,專案早期可以只讓一端的開發人員跟需求,另一端直接“翻譯程式碼”,甚至一個人寫兩端程式碼。

2.3 理清業務再動手

App 作為業務鏈的末端,由於角色所限,開發人員對業務的理解比後端要淺,所謂欲速則不達,重構不能急,理清楚業務邏輯之後再動手。(可以找熟悉業務的同學聊一下 — PD、後端、測試)

3 模組化過程

所謂模組化,是一個分而治之的過程,概念類似於 SOA,首先進行垂直拆分,過程中必然會催生出業務共享的 Common 模組,而 Common 又可以繼續水平拆分,逐漸變薄,直到 Common 消失。

剛開始不需要完美的目標,簡單粗暴一點,後續再逐漸改善。

4.png

3.1 抽取 Common

Common 層服務於所有的上層業務,是通用層,不允許引用業務層程式碼。

  1. 首先把 Common 層用到的 Business 層程式碼下放到各個業務

  2. 然後把多個 Business 之間共用的程式碼提取到 Common 層

  3. 資原始檔的處理方式與程式碼一致

5.png

Common 層作為權宜之計,它的命運是向死而生,最終會誕生出許多功能獨立的基礎模組。而這個過程是漫長的,我們只能在業務隔離的同時,不斷豐富 Common 模組,然後在某個節點將其再拆分成一個一個獨立模組。

6.png

程式碼也逃不出分久必合、合久必分的的宿命。

3.2 業務隔離

業務模組之間不能互相依賴,只能單向依賴 common。

7.png

業務之間存在兩種耦合關係:

  • 頁面耦合

  • 功能耦合

要做到徹底隔離就必須打破這兩種耦合關係:

  • 頁面解耦 - 跳轉協議

  • 功能解耦 - 模組間 RPC

3.2.1 統一跳轉協議

頁面解耦可以借鑑 Web 的設計原理,給業務模組中對外的頁面定義一個 URI,然後頁面之間通過 URI 跳轉。

舉個栗子,A、B 兩個頁面分屬於不同的業務模組,在頁面未解耦之前,A 如果要跳轉到 B,必須要依賴 B 的模組,那麼跳轉程式碼會寫成如下形式:

Android

1.Intent intent = new Intent(getContext(), BbbActivity.class);2.intent.putParcelable(BbbActivity.EXTRA_MESSAGE, message);3.startActivity(intent);

iOS

1.BbbViewController *bbbVC = [[BbbViewController alloc] init];2.bbbVC.messageModel = messageModel;3.[self.navigationController pushViewController:bbbVC animated:YES];

如果 A、B 之間還需要傳遞資料,就要共享常量、Model,耦合繼續加重。

如果我們為 B 頁面定義一個 URI - wsc://home/bbb,然後把共享的 messageModel 拍平序列化成 Json 串,那麼 A 只需要拼裝一個符合 B 頁面 scheme 的跳轉協議就可以了。

1.wsc://home/bbb?message={ "name":"John", "age":31, "city":"New York" }

URL Router 有很多種實現方式,網上資料也是多如牛毛,這裡只提供一種思路。

Android 實現方式

 1. 在 AndroidManifest.xml 檔案中定義 URI

01.<activity02.android:name=".ui.BbbActivity"03.<intent-filter>04.<category android:name="android.intent.category.DEFAULT" />05.<action android:name="android.intent.action.VIEW" />06.<data07.android:host="bbb"08.android:path="/home"09.android:scheme="wsc" />10.</intent-filter>11.</activity>

2. 封裝跳轉 Intent

1.final Uri uri = new Uri.Builder().authority("wsc").path("home/bbb")2..appendQueryParameter("message"new Gson().toJson(messageModel)).build();3.final Intent intent = new Intent(Intent.ACTION_VIEW);4.intent.setData(uri);5.startActivity(intent);

3. 步驟 2 程式碼進一步封裝

1.ZanURLRouter.from(getContext())2..withAction(Intent.ACTION_VIEW)3..withUri("wsc://home/bbb")4..withParcelableExtra("message", messageModel)5..navigate();

iOS實現方式

  1. 通過 plist 檔案儲存 URI 到 Controller class 的對映8.png

  2. 封裝一個根據 URI 跳轉到 Controller 的 SDK

  3. 頁面跳轉

1.[ZanURLRouter routeURL:@"wsc://home/bbb"];

注意事項

  • 兩端協議要保持一致

  • 需要通過工程手段保證頁面 URI 唯一

3.2.2 模組間 RPC

9.png

「業務 A 」與「Remote: 服務端」之間通過 HTTP 或者其他協議進行遠端呼叫,「Remote: 服務端」是服務提供者,「業務 A 」是服務消費者。

對於「業務 A 」來說,「Local: 業務 B」也是服務提供者,但是兩者不存在依賴關係,所以只能通過協議來通訊。

  • iOS 通過 protocol 提供服務,利用 BeeHive 做“服務治理”。

  • Android 通過 interface 提供服務,然後我們模仿 Retrofit 做了一個“服務治理”框架 - ServiceRouter,它的優勢在於可以只在業務提供方的 module 中定義 interface,解耦更徹底。

10.png

4 程式碼管理

如果被隔離的業務模組仍然在一個 Project 中,就無法從“物理”上徹底隔絕程式碼間的相互引用,我們需要從工程上保證業務之間互相獨立。

4.1 程式碼結構

Android (Module)iOS (Project)
11.png12.png

4.2 獨立發版

每一個 subproject 可以獨立發版,然後通過座標依賴組裝成 App,以 Android 為例:

13.png

4.3 獨立 Repo

現在還沒有找到一個很好的程式碼組織形式,所以我們的觀點是:

在團隊規模不大的時候,一個人要 Cover 多個子工程,所以沒有必要獨立 Repo,當一個 Repo 需要多個人 Cover 時可以考慮獨立 Repo。

規模是否獨立 Repo
Developer 1 : N projects
Project 1 : N developers

當解耦方案確定之後,模組化其實就是一個體力活,返工重做便成了家常便飯,所以我們覺得比較好的方式應該是專人負責、一氣呵成

5 詩和遠方

  • 通過移動配置中心動態下發跳轉協議

  • 抽取移動端業務通用 UI 元件庫

  • 主工程可選擇性依賴業務模組