有贊 App 模塊化實戰經驗總結

作者:有贊顏值天團 - 電商移動團隊

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

1. 痛點

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

  • 業務邊界不清晰
  • 通用代碼與業務代碼耦合
  • 代碼、資源文件大量重複
  • 常量滿天飛

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

2. 重構原則

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

2.1 漸進式重構

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

2.2 iOS / Android 互相參考

業務代碼總是驚人的相似,兩端互相參考的過程中,不但可以 Review 代碼,還能加深對業務的理解,可謂一舉兩得。

實踐證明,如果人手緊張,項目早期可以只讓一端的開發人員跟需求,另一端直接「翻譯代碼」,甚至一個人寫兩端代碼。

2.3 理清業務再動手

App 作為業務鏈的末端,由於角色所限,開發人員對業務的理解比後端要淺,所謂欲速則不達,重構不能急,理清楚業務邏輯之後再動手。

可以找熟悉業務的同學聊 — PD、後端、測試

3 模塊化過程

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

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

3.1 抽取 Common

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

  1. 首先把 Common 層用到的 Business 層代碼下放到各個業務
  2. 然後把多個 Business 之間共用的代碼提取到 Common 層
  3. 資源文件的處理方式與代碼一致

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

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

3.2 業務隔離

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

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

  • 頁面耦合
  • 功能耦合

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

  • 頁面解耦 - 跳轉協議
  • 功能解耦 - 模塊間 RPC

3.2.1 統一跳轉協議

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

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

Android

Intent intent = new Intent(getContext(), BbbActivity.class);nintent.putParcelable(BbbActivity.EXTRA_MESSAGE, message);nstartActivity(intent);n

iOS

BbbViewController *bbbVC = [[BbbViewController alloc] init];nbbbVC.messageModel = messageModel;n[self.navigationController pushViewController:bbbVC animated:YES];n

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

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

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

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

Android 實現方式

1. 在 AndroidManifest.xml 文件中定義 URI

<activityn android:name=".ui.BbbActivity"n <intent-filter>n <category android:name="android.intent.category.DEFAULT" />n <action android:name="android.intent.action.VIEW" />n <datan android:host="bbb"n android:path="/home"n android:scheme="wsc" />n </intent-filter>n</activity>n

2. 封裝跳轉 Intent

final Uri uri = new Uri.Builder().authority("wsc").path("home/bbb")n .appendQueryParameter("message", new Gson().toJson(messageModel)).build();nfinal Intent intent = new Intent(Intent.ACTION_VIEW);nintent.setData(uri);nstartActivity(intent);n

3. 步驟 2 代碼進一步封裝

ZanURLRouter.from(getContext())n .withAction(Intent.ACTION_VIEW)n .withUri("wsc://home/bbb")n .withParcelableExtra("message", messageModel)n .navigate();n

iOS實現方式

1. 通過 plist 文件保存 URI 到 Controller class 的映射

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

3. 頁面跳轉

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

注意事項

  • 兩端協議要保持一致
  • 需要通過工程手段保證頁面 URI 唯一

3.2.2 模塊間 RPC

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

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

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

4 代碼管理

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

4.1 代碼結構

  • Android by Module

  • iOS by Project

4.2 獨立發版

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

4.3 獨立 Repo

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

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

  • Developer 1 : N projects => 不建議獨立 Repo

  • Developers N : 1 project => 可獨立 Repo

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

5 詩和遠方

  • 通過移動配置中心動態下發跳轉協議
  • 抽取移動端業務通用 UI 組件庫
  • 主工程可選擇性依賴業務模塊

我並沒有說過上面那句話,不過下面的文章你可以讀一讀 --- 魯迅

  • 有贊 App 動態化配置中心實踐
  • 移動端本地安全解決方案
  • App 中使用 Iconfont 的整套方案
  • iOS WebView 的 Hybrid 框架設計

我們積極擁抱新技術,不給自己設邊界,勇於跳出舒適區

iOS/Android 開發可投簡歷至 liangfei@youzan.com(微信:feelang )

推薦閱讀:

Android 模塊化探索與實踐
webpackJsonp is not defined?
模塊化的概念近幾年為什麼這麼火?
模塊化的意義何在?
如何管理好10萬行代碼的前端單頁面應用

TAG:模块化 | Android开发 | iOS开发 |