背景
抖音 Feed 容器在推薦、關(guān)注、同城、朋友等多個(gè)場景中使用,每個(gè)場景都有自身的邏輯和業(yè)務(wù),最終匯總在 FeedViewController 中,隨著業(yè)務(wù)的迭代,代碼越來越臃腫,面臨如下的問題:
- 容器類(FeedViewController) 有 10000+行,還有十多個(gè)業(yè)務(wù)分類,整體的理解和維護(hù)成本高
- 容器類 框架和業(yè)務(wù)邊界不清晰,框架代碼的修改不收斂和不規(guī)范,業(yè)務(wù)改動(dòng)可能導(dǎo)致線上問題,如數(shù)據(jù)層不收斂導(dǎo)致的問題:自動(dòng)刪除導(dǎo)致一次滑動(dòng)多個(gè)視頻或者自動(dòng)跳轉(zhuǎn)到第一個(gè)視頻等問題
- 容器類 承擔(dān)了推薦、關(guān)注、朋友三個(gè)大場景,細(xì)節(jié)的業(yè)務(wù)邏輯差異較多,目前多業(yè)務(wù)代碼耦合在一起,增加新功能時(shí)需要考慮其他業(yè)務(wù)方,容易引入問題,開發(fā)和測試效率低
- 內(nèi)流容器和外流容器,形態(tài)相似但是代碼分離,主體代碼重復(fù),新增功能時(shí)需要在兩個(gè)類中做重復(fù)開發(fā),如:視頻預(yù)加載優(yōu)化等,開發(fā)和維護(hù)成本高
- 核心功能的監(jiān)控和代碼防劣化的體系不完善
Feed 容器多場景下承載業(yè)務(wù)
Feed 容器承載了基礎(chǔ)功能、直播、登錄、登出、性能監(jiān)控、預(yù)加載等多個(gè)功能。
由于之前沒有做好管控,導(dǎo)致容器中業(yè)務(wù)相互耦合嚴(yán)重,業(yè)務(wù)邊界不清晰,開發(fā)過程中稍有不慎,就會(huì)對(duì)其他業(yè)務(wù)造成影響。

而且隨著業(yè)務(wù)迭代,逐漸呈現(xiàn)劣化趨勢,尤其是對(duì)于新業(yè)務(wù)接入,面對(duì)負(fù)責(zé)的代碼無從下手。
業(yè)務(wù)迭代效率低
由于代碼都在容器類中直接修改,一個(gè)版本經(jīng)常會(huì)有多個(gè)業(yè)務(wù)在容器中進(jìn)行修改導(dǎo)致沖突的情況,此時(shí)就需要多方進(jìn)行 review,保證改動(dòng)不出問題,往往還要平臺(tái)業(yè)務(wù)的同學(xué)進(jìn)行支持,業(yè)務(wù)的整體迭代效率比較低。

防劣化&監(jiān)控缺失
業(yè)務(wù)耦合,對(duì)代碼改動(dòng)沒有監(jiān)控,導(dǎo)致 FeedViewController 越來越膨脹。因?yàn)闆]有合理架構(gòu)導(dǎo)致無法做拆分,代碼劣化越來越嚴(yán)重,而且基于現(xiàn)狀無法進(jìn)行防劣化。
目標(biāo)方案
為了解決上述問題,首先設(shè)定好目標(biāo),然后根據(jù)目標(biāo)提出解決方案,最終落地實(shí)現(xiàn),驗(yàn)證目標(biāo)是否達(dá)成。
目標(biāo)
- 架構(gòu)分層,明確每層職責(zé),容器和業(yè)務(wù)解耦,多業(yè)務(wù)之間解耦,做到容器和業(yè)務(wù)各自閉環(huán);
- 業(yè)務(wù)組件可插拔,不同場景支持靈活的組合和擴(kuò)展業(yè)務(wù)組件;
- 搭建監(jiān)控體系,實(shí)現(xiàn)穩(wěn)定性、性能、問題定位,建立看板,實(shí)時(shí)了解各項(xiàng)指標(biāo);
- 防劣化,容器和業(yè)務(wù)分倉隔離,收斂維護(hù)人員;
思路
根據(jù)上述的目標(biāo),從下面四點(diǎn)進(jìn)行思考和設(shè)計(jì):
- 明確業(yè)務(wù)開發(fā)痛點(diǎn),多業(yè)務(wù)合作開發(fā)效率低、設(shè)計(jì)不合理模塊使用成本高等;
- 自上而下設(shè)計(jì),保證整體業(yè)務(wù)架構(gòu)設(shè)計(jì)的合理性,明確優(yōu)化方向;
- 分層開發(fā)和上線驗(yàn)證,降低上線風(fēng)險(xiǎn)和全量成本;
- 架構(gòu)防劣化,收益可衡量;
方案
針對(duì) Feed 容器內(nèi)部多場景、多業(yè)務(wù)耦合導(dǎo)致整體維護(hù)困難,新業(yè)務(wù)接入成本高的問題,首先按照?qǐng)鼍啊I(yè)務(wù)和功能維護(hù)進(jìn)行拆分梳理。在拆分完成后為了方便各個(gè)業(yè)務(wù)進(jìn)行維護(hù),設(shè)計(jì)了 ControlerKit 工具實(shí)現(xiàn)了生命周期方法的分發(fā),并且通過 Context 進(jìn)行狀態(tài)管理,實(shí)現(xiàn)了各個(gè)業(yè)務(wù)間的通信和狀態(tài)維護(hù)。
整體架構(gòu)

基礎(chǔ)容器
Feed 基礎(chǔ)容器,采用組件化框架,支持基礎(chǔ)組件和業(yè)務(wù)組件的動(dòng)態(tài)組合和擴(kuò)展,由業(yè)務(wù)無關(guān)、統(tǒng)一的列表形態(tài)組成,通過數(shù)據(jù)驅(qū)動(dòng)頁面展現(xiàn)。同時(shí)對(duì)外暴露生命周期事件,方便組件進(jìn)行監(jiān)聽。其中基礎(chǔ)容器由平臺(tái)方進(jìn)行統(tǒng)一維護(hù),并提供了完善的監(jiān)控體系,方便進(jìn)行問題的定位和追查。
基礎(chǔ)組件
Feed 容器的基礎(chǔ)組件部分,采用的方式是平臺(tái)方統(tǒng)一進(jìn)行維護(hù)。目前的基礎(chǔ)組件,主要包括播放控制、播放策略優(yōu)化、列表預(yù)加載以及頁面管理等。
其中,全屏 Feed 相關(guān)的基礎(chǔ)組件,為多業(yè)務(wù)共用,具備可復(fù)用、可擴(kuò)展等優(yōu)勢。
業(yè)務(wù)組件
業(yè)務(wù)組件是和業(yè)務(wù)強(qiáng)相關(guān)的組件,業(yè)務(wù)方可以根據(jù)自身的需要進(jìn)行靈活定制,組件本身可插拔,由各業(yè)務(wù)方進(jìn)行維護(hù)。
應(yīng)用場景
業(yè)務(wù)方基于 Feed 容器,組合業(yè)務(wù)組件和基礎(chǔ)組件構(gòu)建的頁面,在構(gòu)造過程中可以基于配置文件實(shí)現(xiàn)容器的定制,比如推薦和關(guān)注。
容器化工具
多個(gè)業(yè)務(wù)耦合在同一個(gè)容器中,導(dǎo)致容器類越來越臃腫,一方面造成各方同時(shí)維護(hù)越來越困難,另一方面對(duì)于新業(yè)務(wù)和新同學(xué)接入十分不友好,需要花費(fèi)很多時(shí)間熟悉上下文以避免改動(dòng)對(duì)其他業(yè)務(wù)造成影響。
為此設(shè)計(jì)了 ControllerKit 庫,該庫實(shí)現(xiàn)了復(fù)雜頁面的分發(fā),解決 ViewController 臃腫問題,規(guī)范代碼拆分標(biāo)準(zhǔn),提供分發(fā)方法的能力。各個(gè)接入方按照規(guī)則注冊(cè)后,實(shí)現(xiàn)自己關(guān)心的生命周期方法,并在方法中實(shí)現(xiàn)對(duì)應(yīng)的邏輯即可。

ContainerViewController
ContainerViewController 是容器 ViewController,實(shí)現(xiàn)了 ContainerProtocol,保存了上下文環(huán)境,負(fù)責(zé)了各個(gè)生命周期方法的分發(fā)。
ContainerProtocol
聲明了容器對(duì)外提供的屬性和方法,方便各個(gè) SubController 進(jìn)行訪問。
ControllerProtocol
聲明了基礎(chǔ)的聲明周期和共有的方法。
Controller
Controller 是將 ViewController 中的代碼拆分出來的子模塊,可以接收分發(fā)出來的 viewDidLoad、viewWillAppear 等生命周期及自定義方法調(diào)用,還可以向 ViewController 中添加子 View。
ControllerManager
ControllerManager 負(fù)責(zé) Controller 的注冊(cè)、管理、方法分發(fā)。通過 classNameArray 返回 Controller 的字符串類名數(shù)組即可,可以支持 Controller 在其他倉庫的能力
Manager 需要聲明分發(fā)的 Controller 協(xié)議,只需要聲明,不需要實(shí)現(xiàn),Manager 內(nèi)部會(huì)通過消息轉(zhuǎn)發(fā)機(jī)制統(tǒng)一分發(fā)。
各角色之間的關(guān)系
ContainerViewController 實(shí)現(xiàn)了 ContainerProtocol,并持有 ControllerManager,各個(gè)子 Controller 注冊(cè)到 ControllerManager 中,各個(gè) Controller 可以通過 ContainerProtocol 訪問容器的能力,ControllerManager 通過 ControllerProtocol 里面聲明的方法進(jìn)行分發(fā)。
比如:ContainerViewController 初始化后調(diào)用 viewDidLoad 時(shí),會(huì)通過 ControllerManager 依次分發(fā)到實(shí)現(xiàn)該方法的 controller 中,各個(gè) Controller 在自己的 viewDidLoad 方法中實(shí)現(xiàn)自己的邏輯即可。
Controller 優(yōu)先級(jí)
- 方法分發(fā)優(yōu)先級(jí)按照數(shù)組提供的順序,因此更基礎(chǔ)的 Controller 應(yīng)排在前面
- 優(yōu)先級(jí)由注冊(cè)順序決定,因此不同方法優(yōu)先級(jí)無法調(diào)整,也不希望有調(diào)整,無法滿足時(shí),通過其他方式實(shí)現(xiàn)
Feed 容器的實(shí)現(xiàn)
根據(jù) ControllerKit 對(duì) Feed 容器的類結(jié)構(gòu)改造如下所示

- FeedViewController 作為容器,實(shí)現(xiàn)容器能力,對(duì)外通過 FeedContainerProtocol 被訪問
- Controller 對(duì)應(yīng)業(yè)務(wù)組件
- FeedControllerManager 負(fù)責(zé)組件的注冊(cè)、管理和事件的分發(fā)
基于 ControllerKit 的設(shè)計(jì)和實(shí)現(xiàn)
各個(gè)類和協(xié)議的介紹:
FeedContainerProtocol
- 容器層通過 FeedContainerProtocol 對(duì)外提供能力
- 避免業(yè)務(wù)方直接訪問和修改容器類
- 該協(xié)議提供了業(yè)務(wù)層需要的各種能力和接口
- 由平臺(tái)方進(jìn)行維護(hù)
FeedControllerProtocol
- 業(yè)務(wù)層協(xié)議通過 FeedControllerProtocol 聲明
- 定義了各個(gè)生命周期相關(guān)的方法,被各個(gè)業(yè)務(wù) controller 實(shí)現(xiàn)
- 各個(gè)實(shí)現(xiàn)業(yè)務(wù)只需要在對(duì)應(yīng)的生命周期方法中增加自身的邏輯即可
- 被注入的 controller 會(huì)在相應(yīng)的時(shí)機(jī)被調(diào)用到
- 業(yè)務(wù)自閉環(huán)
Context 與 ContainerProtocol 的定位和區(qū)別
- FeedContainerProtocol 用來給 controller 提供 FeedViewController 實(shí)現(xiàn)的能力
- FeedContext 中存放 Controller 共用的狀態(tài)
- 兩個(gè)都能實(shí)現(xiàn)通信,但 context 更偏重于狀態(tài),而 ContainerProtocol 更偏重于能力,比如頁面滾動(dòng)、數(shù)據(jù)刷新
業(yè)務(wù)組件定義
- 定義業(yè)務(wù) Controller 類
- 實(shí)現(xiàn) FeedControllerProtocol 協(xié)議
- 在對(duì)應(yīng)的生命周期方法中實(shí)現(xiàn)對(duì)應(yīng)的業(yè)務(wù)邏輯
- 若 FeedControllerProtocol 不滿足情況時(shí)根據(jù)之前說明方式在協(xié)議中增加新的生命周期方法,同時(shí)同步增加到 FeedContainerProtocol ,以便分發(fā)
重構(gòu)后業(yè)務(wù)迭代方式

- 框架由平臺(tái)業(yè)務(wù)架構(gòu)方維護(hù)
- 其他業(yè)務(wù)的框架擴(kuò)展需要提交到架構(gòu)方,由架構(gòu)方開發(fā)
- 其他業(yè)務(wù)提交的方案和修改,交由架構(gòu)方 review
- 業(yè)務(wù)方的代碼,業(yè)務(wù)方自閉環(huán)
防劣化建設(shè)
為了防止隨著業(yè)務(wù)的迭代,F(xiàn)eed 容器逐漸劣化,需要進(jìn)行防劣化建設(shè)。首先進(jìn)行框架和業(yè)務(wù)分倉:
- 代碼隔離,修改權(quán)限收斂;
- 框架部分,線下做 Pipeline 準(zhǔn)入,Lint 檢查是否符合容器規(guī)則; 業(yè)務(wù)方修改容器代碼,review 通過后才能合入

新方案優(yōu)勢
- 業(yè)務(wù)解耦,明確了業(yè)務(wù)和容器的職責(zé),邊界清晰
- 降低 FeedViewController 維護(hù)成本
- 減少新業(yè)務(wù)接入成本
- 方便做防劣化
接入示例
以下以興趣選擇和業(yè)務(wù)為例,介紹新老業(yè)務(wù)的接入。
新功能接入 - 興趣選擇
興趣選擇是新的類型的卡片,需要進(jìn)行卡片注冊(cè)并處理相關(guān)邏輯。

歷史方案
FeedViewController 直接進(jìn)行修改,包括如下內(nèi)容:
- 增加狀態(tài)管理屬性
- 需要在 tableview delegate 和 scroll 滾動(dòng)等多個(gè)方法中增加相應(yīng)的處理邏輯
- 處理注冊(cè)卡片邏輯
新方案
抽取單獨(dú)的業(yè)務(wù) Controller
- 在生命周期方法中處理興趣選擇相關(guān)邏輯
- 業(yè)務(wù)相關(guān)的屬性在 Controller 中聲明和維護(hù)
Controller 注冊(cè)到 ControllerManager
在對(duì)應(yīng)的 Controller 中進(jìn)行自己的業(yè)務(wù)處理即可,不需要了解容器本身的其他業(yè)務(wù)邏輯
存量功能拆分 - Feed 監(jiān)控
Feed 監(jiān)控功能在 FeedTableVC 中處理了很多業(yè)務(wù),而且這些邏輯也其他業(yè)務(wù)存在著耦合。
- 網(wǎng)絡(luò)請(qǐng)求監(jiān)控和數(shù)據(jù)處理
- 頁面滾動(dòng)
- 播放處理
- ...

采用新方案進(jìn)行拆分
首先創(chuàng)建 FeedMonitorController,增加業(yè)務(wù)相關(guān)的屬性、生命周期方法中實(shí)現(xiàn)對(duì)應(yīng)的邏輯,之后抽取單獨(dú)的業(yè)務(wù) controller 在生命周期方法中處理熟人相關(guān)邏輯。同時(shí)注冊(cè)到 controllerManager 中,并設(shè)置 AB、原有代碼判斷 AB。上線驗(yàn)證,全量后刪除容器老代碼。之后業(yè)務(wù)自閉環(huán),再進(jìn)行迭代時(shí)直接在 FeedMonitorControlle r 內(nèi)容修改即可。

當(dāng)前進(jìn)展&后續(xù)規(guī)劃
規(guī)劃和節(jié)奏

1 | 2 | 3 | 4 |
梳理現(xiàn)狀; 重構(gòu)方案設(shè)計(jì)和評(píng)審; | 新增功能基于新組件開發(fā); | 業(yè)務(wù)接口合理化:Feed 容器對(duì)外暴露能力,業(yè)務(wù)調(diào)用; | 組件化框架橫向應(yīng)用,詳情頁 Feed 等使用新架構(gòu) |
重構(gòu)后的收益
- 業(yè)務(wù)解耦后,容器本身穩(wěn)定,業(yè)務(wù)方各自維護(hù)自身業(yè)務(wù),提高了整體的穩(wěn)定性
老容器 | 新容器 |
因?yàn)闃I(yè)務(wù)耦合,需要了解 Feed 的結(jié)構(gòu)和多業(yè)務(wù)的細(xì)節(jié),新同學(xué)熟悉的時(shí)間需要 2 天左右;在實(shí)現(xiàn)過程中,由于多個(gè)業(yè)務(wù)同時(shí)進(jìn)行迭代,相互影響,質(zhì)量無法保障 | 只需要在自己的業(yè)務(wù) Controller 開發(fā)即可,無需關(guān)心容器的結(jié)構(gòu)以及其他業(yè)務(wù)方,極大的提高了開發(fā)和迭代效率;改動(dòng)不影響其他業(yè)務(wù)線的代碼,保障了代碼的穩(wěn)定性 |
- 全量業(yè)務(wù)在業(yè)務(wù)組件中實(shí)現(xiàn)了自閉環(huán)
版本進(jìn)行了映射
版本 | 新方案 MR | 老方案 MR | 老方案占比(老 MR/(新 MR+老 MR)) |
1.7 - 2.0 | 39 | 19 | 32.8% |
1.3 - 1.6 | 31 | 18 | 46.15% |
0.9 - 1.2 | 25 | 13 | 34.21% |
0.5 - 0.8 | 16 | 23 | 58.9% |
0.1 - 0.4 | 12 | 19 | 61.2% |





























