百度 App Feed 流業務架構變遷思考和升級實踐

作者 | 李哲浩

整理 | 郭磊

審校 | 劉燕

本文整理自百度技術專家、推薦產品研發部架構師李哲浩在 2022 年 8 月 ArchSummit 全球架構師峯會北京站的演講分享,主題爲 “百度 App Feed 流業務架構變遷思考和升級實踐”。

本次分享主要從五個部分展開:第一部分介紹我們過去架構的概況,第二部分介紹在經歷一些環境的變化後,架構上有哪些改變,第三部分介紹我們團隊對於架構設計的一些思想和原則,第四部分例舉幾個架構場景的實例,最後介紹架構維護控制相關的內容。

過去的架構漫步

我們過去一開始的架構是一個個小作坊,比方最開始的時候有一個頻道頁叫頻道 A,然後假設我們突然想做一個進攻的項目,比如想去創建一個垂類頻道 B,這時候我們就會把整套代碼拷貝過去,因爲我們過去採用的是 MVC 架構,如圖所示,整個架構都拷貝了一份,架構只剩下了概念,沒有實體架子支撐,由於這時往往都是一種 “征戰” 的狀態,時間比較緊迫,當時的用例也不多,迭代的深度也不夠,覺得還好,可以實現快速開發,基本夠用。

後來我們出現了人手不夠的情況,PM 對發版有了更高的要求,不再滿足於隨版,而希望去 “非隨”,或者說雙端變成單端想加快迭代效率,這種背景下,我們很多頻道已經開始採用類似 RN 的方案,或者基於 Hybrid 上 H5 的方案去創建。

再這之後,中臺的概念在各大公司飛起,大家一塊見證,我們也不例外,從公司層面做中臺化的架構調整和延伸,我們的業務也搭建中臺組件平臺。由於之前並沒有梳理和建設好合理的耦合邊界,各種似同非同的架構,想要響應公司號召,短期的輸出方式,我們基本上只能選擇整體輸出(文檔也沒法看),也就是說,某個 APP 要創新孵化的時候,我們會把整套組件打包成一個大的組件(或者說 SDK)提供給用戶,他們也想快速上線也很急,那就讓他們基於原碼去定製改造並使用,就是真實的共建仍未產生。

我們在實踐後期發現,當在頻道 A 裏做了一些實驗,證明了某個行爲有正向的收益後,頻道 B 也想用怎麼辦?如果從頻道 A 裏把這個能力複製到頻道 B,當這種操作多了以後,就會出現很多捉襟見肘的情況,系統會變得比較脆弱,當代碼一直在拷貝複用時,很快會變得臃腫,但一直都沒有去做大的重構或者架構升級,所以遺留代碼、技術債也就堆積如山。以上是過去架構的狀況。但不是說拷貝複用一定是壞的,咱後面再述。

大環境的衝擊

後面大環境發生了變化,首先肯定是公司內部的組織變化帶來的老問題加劇,新問題產生,然後是業界技術和架構上的成果對我們視野上的變化。後來我就潛心研究了一下行業內先進的架構模式,看是否能幫我們解決存在的問題。不過這部分我們放在第三部分重點詳述。還是先介紹下現在內部組織結構的情況。下圖是 19 年架構升級時的組織結構。僅作爲示意。

我們是中間部分的 Feed 團隊,中間部分並立的還有一個搜索團隊。

上游有兩個團隊依賴於我們這兩個團隊,比如說商業團隊和其它產品線的一些團隊會依賴到我們的 Feed,比如,我們上游的產品線期望能有快速孵化 app 工廠的能力,同時又對包體積比較敏感,希望 Feed 這邊可以能找到組件動態裁減和拼裝,在這種情況下,我們需要把粒度做的更細。商業這邊是獨立的團隊,他們有他們的關注點和任務,Feed 迭代又快,不清楚 Feed 內部代碼的很多細節,直接改容易出問題,但又有很大的概率要織入到 Feed 代碼來實現一些效果,所以非常痛苦。這就要求 Feed 這邊將各種時機做成穩定的回調接口,或者挖個空讓他們填下。

另一方面,可能每個公司都會搞一些類似 RN 的定製方案,我們的叫 Talos,現在已經完全跟原來的 RN 完全不一樣了,但一開始起步就是這樣開始的,像美團有一個 MRN,阿里有 Cube,大家都做差不多類似的定製研究,並作出一定的創新。再比如我們也研究 Hybrid,他們可能也在研究 Hybrid,我們有一個 OEM Mapping 的方案叫 Crius,他們可能也有一個類似的方案,我們也會調研現在比較前沿的 Flutter、KMM、Jetpack Compose 這些東西,去關注並持續落地到某個場景中去。所以慢慢地,我們就有了自己的組件化、動態化、插件化這些一整套框架,這些大部分是我們依賴的下游的基礎平臺提供的,比如說他們現在就在做 DevOps 大平臺,當然也有業務團隊下沉的基礎設施。

那作爲業務團隊,我們沒有必要重複造輪子,只需要運用這些基礎設施的能力來助力業務的快速發展。

另一個問題,我們現在整個 Feed 團隊有幾十上百號人,怎麼合理分工,另外測試人員越來越少,整個研發流程主要依賴手工測試,這樣每次發版迭代的時候就會突顯出一些問題,比如會阻塞整體的開發效率,但是有的人卻仍處在空閒。不僅如此,團隊間的責任邊界意識會越來越清晰,經常扯皮決定哪方去改,但是代碼上的邊界可不一定是這樣清晰,經常你中有我,我中有你。

所以,我們想來一次業務架構的升級換代。去擁抱新的變化。

採用的架構思想和原則  

架構影響因素  

我們在架構升級的時候,考慮了哪些因素呢。首先圍繞業務價值是必須的。否則所有的工作都失去了開展的必要。

組織架構:組織結構決定了整個邊界,比如說我們一開始有 Feed 團隊和視頻團隊,原來是一個團隊,但後來被分開了,分開以後就會有一些邊界劃分,或者說兩個庫之間會脫的越來越開,這個時候應該做出合理的共建和隔離權衡。

投入成本比  

說到量化,我們展開聊聊投入成本比,比如我們的節奏大概是這樣的,一個需求發佈了,我是做這個需求的人,今天週一,我評估大概 4.5 天差不多能完成,但是我會給自己留一些 Buffer,所以名義上可能將此任務估成六天工作量,但是可能下週二就需要上線,這樣 QA 說他們需要一天的人力測試,這種情況下,我只有本週 5 天的時間可以安排了,倒排以後我只能按 5 天來排期,言外之意我預計要自己加班一天,實際開發的時候我發現 4 天就搞定了,搞定了以後我並沒有閒着,因爲這之間,雖然出了線上問題,我還在做上一個版本的 bugfix。

這一週下來後,測試花了一天的時間,當然大量的時間可能是在借調人力,因爲 QA 人力也不足,不過整體下來平臺上統計的可能是名義粗估和排期,但真正的實際粗估以及實際真正所用天數可能是無法體現的,那麼這到底怎麼去衡量?如果無法衡量,那比如說我們的架構,或者說我們的某個設計得到了一些改進之後,它的效率有沒有提高?維護性有沒有變好?怎麼確定?(當然事實上無法準確量化也是共識)

我們希望重塑原始數據的積累情況,比如說剛剛那個例子,假設說需要一個人投入一個 Q,但是如果領導和 PM 感知不到效率的提升,就不會投入資源,如果沒有這些干係人的持續投資,很多上馬的研發項目可能也會停擺,造成更大的岔口。而我們的平臺統計恰恰顯示沒有提升。而有些事不做技術債會越來越多,直到系統性風險。

可是由於沒有采用故事點或者理想人天等形式,而是直接採用了排期這種手段,總不應該去怪團隊成員怎麼不把真實的估計填上去。那是因爲他們也無法準確的估計工作量,而且也要適應請假、應對突發情況,無盡的會議。如果沒有 buffer,這個壓力強度就太高了。

或者讓團隊成員去列下粗估的構成,讓其他人都看看是否合理,但是考覈什麼就會得到什麼,這種檢驗不信任已經造成了不信任,不僅無法揪出不合理,而且會造成士氣低下。

也可能以概率替代全局,比如某個任務原來需要 20 天,我們做了某種改進,然後再執行相似任務只花了 4 天。然後把這個收益擺上去要投資。完全不談命中,這讓人信服嗎?任何的改進都有概率會命中失敗,比如某個端能力缺失,開發這個端能力需要 8 天,那優化收益就變成 12 天。僅把最佳情況列上去是有問題的。更何況,有的情況是,我們做了改進,一年都用不上。

在第五部分維護控制階段,我再談談我們有限解法。

團隊成熟度  

在團隊成熟度方面,以我這幾年在多個團隊的經歷和變遷。我發現,一開始新業務會處在原型階段,可能會有很多需求迭代,這時通過繼承也好,或者拷貝也好,怎麼快怎麼做。

在後面的階段,業務會發生擴張,遍地都是 “黃金”,大家就開始做一些通用化的建設,但這個時候在一個比較大的範圍內,還是沒有人管的,還沒有非常好的治理手段,導致大家會有重複造輪子的現象,或者說核心的部分,大家都不想讓別的團隊來摻和,於是處於一種野蠻的生長狀態,大家都會去爭搶業務邊界。

再後面大家可能會意識到內耗,去搞這麼多框架這麼多組件來佔用人力維護是一筆不小的成本,而且從上而下,可能架構師會去做一些合併去重的事情,這時候的節奏就是爭搶標準,爭搶輸出,比如說我輸出多少個組件,你輸出了多少到哪,這樣我的組件或者框架可能變得安全一些(不會被合併),這時候大家的想法是你造一個汽車,我就造一個航母,我這個能力更強,各方面會更好。此時此刻,對 ROI 尚且寬容。

到最後就會進入到一個萎縮階段,因爲我們可能從一個業務線,或者說一個進攻的重點,遷移到另外一個重點,原來那個重點上只會處在維護的階段,這時候各方面都已經成熟落地了,再做一點點改進的時候都要考慮 ROI,當真的有較好收益時纔會被允許去做一些重構或者升級。

業務複雜度  

我們團隊也經歷了從 MVC 這種小作坊的模式到後面能駕馭更大複雜度設計的轉變。我們一直在說複雜度,什麼是業務複雜度?

比如說我們在做一個轉換器的插件,將 RGBA 的顏色數組轉換成 YV12 格式,可能技術很複雜,可能會用匯編,可能會用 C++,可能會用 Erlang 運作多處理器,或者是通過 OpenCL 充分利用 GPU 的能力,但它的業務複雜性卻很低,因爲我們很明確知道它就是要做這個單一的事情。可以類比問題域的複雜度很低,但解決方案空間的複雜度很高(爲了追求某種質量屬性,這裏是性能)。

再比如我們在有些情況下做一個簡單的彈窗,這種彈窗只是調用系統 API 就可以了,技術複雜度很低,工作也就是拼接一個字符串,也非常簡單,但由於要考慮上百種不同的情況,或者不同情況組合之間有一些互斥,在有些協調和相互作用的情況下,文案都不一樣,這種情況下業務複雜度會變得很高。也就是說我們分支很多,熵會很大。

很多業務在經過深入迭代以後,要實現一點微小的數據的提升都要做很多實驗,很多微小路徑的改變都會導致整個業務複雜度增加,它的分支會越來越多,這難以避免。我們稱這爲 “業務雪球”。

再比如,文件寫入也可能有很多異常要處理,但文件處理本身是你要專注的業務嗎?如果我們本身是寫一個文件處理軟件,那可能就是,如果對於這個產品來說,你告訴我是否成功就可以了,至於有多少種錯誤碼我不關心,我只 try catch 一下就可以了,也就是說,它是不是你的核心業務是一方面。

團隊成熟度和業務複雜度  

說到團隊成熟度和業務複雜度,如果我們用平面切割思維,它可以組合成很多方式:

比如說是簡單的業務,或者說複雜的業務。當然這裏的邊界不做量化的定義。度量衡交給您。就我們基礎體驗小組,大家都能認可刷新業務是複雜的,首先出現問題影響極大,所以開發者強度高,壓力大,而且沒有單測覆蓋,也不會輕易去做改動,業務邏輯分支,幾屏幕的思維導圖放不下。沒有人能不看源碼枚舉出所有 case。

橫軸是我列的三種人設,管理學上有經濟人、社會人、複雜人假設。你可能會想,這人怎麼這樣,帶有色眼鏡看別人,是不是心理太陰暗?這倒不是說對某個具體的人做出假設或影射。而是想簡化架構服務人羣,進而挑選更合適的質量屬性和選型決策。而且一個人在不同的階段,面對不同的項目,可能表現爲某種人設成分多一些,即一個真實的人是複雜的,可能同時映射到多種假設。事實上兩邊的人設都是理想態極值。你可以類比星座。我們稍微展開下。

第一種是自由人,有一些人不太服從團隊的指揮,他們非常願意去鑽研,或者說實踐一些新的東西,也可能就是單純喜歡自由的空氣,他們普遍非常聰明,但他們普遍也看不上工程化的價值,覺得是束縛,對於大部分的設計,他們認爲是華而不實,花裏胡哨,也可能不會讓別人污染侵犯自己的代碼 “領地”。在我工作的這十多年,不同範圍內的不同團隊裏,總能看到這一類人,這種情況下,如果是簡單業務就放任他,能有個人做就不錯了,而且你要相信,這類中的大部分的人 bug 率一直都是很低的。如果是複雜業務,這種人多了很容易失敗,我們很多局部的架構已經因爲這種情況而失敗過了。表現爲離開他,很難有人接起來,除非重寫。

第二種是工具人,工具人基本上就是處於服從的狀態,你讓我怎麼搞就怎麼搞,我們有一套規則,我就按照這個規則來做就好了,我不管它複雜還是不復雜,繁瑣不繁瑣,這種情況下,如果是簡單業務,我們給他打造一塊樣板間就可以。如果是比較複雜的業務,最好是前期時做一些稍微的、適當的過度設計,但這個度還是要把握好,不是做太大的過度設計,不然維護成本會激增,但肯定要稍微過度設計一下,因爲他只會拷貝你的代碼或思路來疊加他們的邏輯。

上百號人的團隊,有時候很多東西真的是管不過來的,哪怕培養了一批中間力量來承上啓下。有很多的 CR 情況沒辦法感知到,當真正感知到的時候已經晚了。比如說我們原來的 Feed 流它有一些卡片,這些卡片有一百多張,當時解析數據的 Model 用的是安卓的一個框架,發現這個框架可能有一些問題,即便是有一些問題,但沒有人去關心,很多人就開始迭代這些代碼,到最後出現問題,你會發現根本就改不過來了,現在的話,由於 ROI 不高也不會去改它,但是每個新人入職看到這個都會嗤之以鼻一下。

最後一種是敏捷人,這是一個比較理想的狀態,敏捷人和自由人有什麼相同點?我覺得他們的特點都是不去遵守規則,而是堅持他們認爲正確的原則和設計理念。比如我們原來的規矩是每個人 cr 數都要達到多少多少。你知道的,古德哈特定律,考覈什麼就會得到什麼。對於這兩種人,他們都會反對盲目的 cr 數考覈,但是自由人的原則往往 cr 沒有用,阻礙他們寫代碼。而敏捷人更能認識到 “有 cr 比沒有 cr 好,沒有 cr 比有 cr 好” 的這種觀點,進而聚焦到 “提升質量” 這件事本身。這意味着敏捷人不止於打破規則,而且要進行推廣並影響更多的人,使新的實踐成爲新的規則。而自由人讓你看到的是多套不同的凌亂的東西。

我們針對簡單業務做簡單設計,簡單設計是 Kent Beck 提出來的,就要遵守四個原則,即通過測試、消除重複,最小元素表達和更少代碼元素,這樣就會得到一個相對可以的設計,因爲我們大多數人不是設計大師,不是搞設計藝術的人,能得到一個平庸的,大家都認可的,可以量化的設計即可。

如果是複雜業務,我們更多的還是看看能不能建立領域模型,Martin Fowler 的企業應用架構模式裏提到了像事務腳本、領域模型、表驅動等概念,因爲我們某些領域比較複雜,在這種情況下需要知道如何梳理我們的領域,領域模型可能是一個前進的方向,它會對整個業務的梳理起到一些作用。

### 什麼應該是模型  

那什麼是領域模型?什麼是業務模型?業務模型一般認爲是狀態(包括場景或環境、數據表示)、策略(包括錯誤處理)和操作的應激集合。

我先說下模型,對於一個簡單的 MVC 架構,那 M 就是一個模型,我工作的這十多年裏,我發現大多數人會把 Server 下發的數據完全當成成一個模型。

我舉個例子,比如說 is_vip 這個字段,is_vip 下發了 1,就會有對應的一個字段是 is_vip 爲 1 這樣的字段,但這種在我們看來只是一個 DTO,即一個數據傳輸對象。

還有另外一種是把數據庫裏面的字段作爲一個真正的模型的一部分,比如說安卓經常推廣一些框架,比如 Room 框架,你如果用 Room 框架會發現,框架會讓類裏面每一列是一個字段,在這樣的情況下,很多時候列名和業務模型就不一致性了,比方說我們現在這個 case 裏邊只存了兩列,第二列是一個 Json,我們要在這個字段裏面定義一個 String 這樣一個字段嗎?可能不是。也有可能會把這個展現模型作爲一個模型,如果是 VIP,就要標個紅色,如果不是就標黑色,紅和黑可能會是一種展現特有的特點,把這個也會作爲一個模型,但這些可能都不是業務模型。

我們希望的業務模型是能夠和 PM 溝通的時候,能夠作爲一個概念去說的,比如我們希望如果是 VIP 就要做解鎖,做刷新,這種情況下,這幾個東西 PM 能聽的懂,業務邏輯裏也能夠寫明,我們把這個規則表達好,具體這個規則是怎麼實現的,就不是那麼重要了。我們只需要知道要存一個數據,具體是怎麼存的,不是業務需要關注的東西,“我們要去存它”這件事本身以及 “什麼時機” 可能更重要。

我們設想一個簡單業務,隨着它變得複雜,關於模型邊界這塊的問題,我們在做什麼:

首先,我們在協調存儲模型和業務模型不一致的矛盾。比如當對一個列表進行增刪改成時,不太容易直觀將這些反映到存儲設施上,除非全刪全存。再比如當對性能有較高要求時,數據庫的列和模型的字段往往對應不上,因爲可能需要預讀。

其次,我們在協調下發模型和業務模型不一致的矛盾。比如 server 下發了三批數據,但是 server 可能是無狀態的,不太容易知道給某個設備下發了這三批數據,並根據展現上報情況做展現去重,即便知道可能也有延遲,但是端有此上下文。再比如業務模型需要一個布爾概念,但是 server 下發了數值或字符串概念。

再次,我們在協調展現模型和業務模型不一致的矛盾。比如業務模型可能表現爲多個布爾求值,但是展現可能歸結合併爲某個控件的顯隱或者字體粗細。

比如可複用視圖的控件(RecyclerView)可能要求滑動窗口模型(去適配它的 Adapter)。再比如展現模型是按照窗口頁面的粒度組織,但是業務模型可能要求連貫的跨越這種粒度。

然後呢,我們也可能會去協調對內業務模型和對外業務模型不一致的矛盾。

比如對外提供了一套 API 和能力集,需求變化導致內部已經使用了新方案,但仍需要兼容使舊方案工作。比如非完整複用帶來一定的擴展定製訴求,要求在你的業務模型上做一些埋點和鉤子供外部定製,以匹配外部方的業務模型,而這些埋點和鉤子不存在於你的真實業務流程。

我們也可能會去協調不同業務模型間需要轉換翻譯的矛盾。比如 SDK 基於自己的模型提供了幾個必要通知,但是你的模型對此無明顯感知,需要做同樣的處理。

再比如不同的領域團隊間的代碼基可能是鬆散耦合的、並行開發的,關注點也不同,使用的數據結構也可能不同。

最後,我們極可能也在協調技術模型和業務模型的矛盾。這體現在協調性能要求對業務模型泄露的影響。比如業務模型設計爲以頁面爲上下文,即頁面創建時生成數據,銷燬時丟棄數據。但性能原因可能在冷起階段進行提前處理、預處理部分邏輯,這要求頁面創建前生成數據。這也體現在人的理解和計算機的矛盾。比如你的意圖是交換兩個值,但是你需要定義第三個變量來存儲臨時值。

Martin Fowler 將業務模型設計拆解爲概念模型、契約模型、實現模型。你的意圖,即交換兩個值,這是概念模型,是領域語言,可以與其他角色共享;然後你設計了一個表明意圖接口層 API 叫作交換,這是契約模型了,因爲此時可以與模型的其他組成部分產生關聯,與多個模型產生協調,這裏已經很好的隱藏了技術信息;最後我們通過臨時變量存儲的方案完成了交換功能,這屬於實現模型。

在這類問題上,我們傾向於在模型驅動開發。假設我們做一個彈窗,可能有很多不同的規則,可能有的是幾天彈一次,有的是每次冷起彈一次,對於很多端上同學,將採用 UI 驅動開發的方式,這勢必在 UI 裏或者從 UI 導到控制器裏邊去做 if-else 判斷。但是如果說模型驅動,那麼首先它會去寫個類來描述這個規則,彈窗可以簡單的用 show 接口方法代替,彈什麼此時不重要,把這個 UI 細節往後推,推到最後纔去考慮這個事情。如果是這樣驅動,那麼 TDD 測試驅動開發是可能的,容易的。讓一個程序員把所有的邏輯跑通再事後去寫單測是乏味的,因爲他知道結果是啥,所以寫單測也是草草找個場景敷衍。

另一個是這個東西其實依賴於程序員的開發習慣,他們需要簡單的業務去產生慢慢改變自己,如果總是去支援一個特別着急的需求,它可能迫於時間壓力傾向於採用他所熟悉的開發方式,那其實根本就不可能達到這種效果。

KMM 的啓示  

我們再看下 KMM,KMM 是 Kotlin 推出的一個跨平臺的東西,它最推薦你共享的是業務邏輯,像平臺訪問、前端交互它都希望你遵守原來平臺的規範,或者說 UI 展示之類相關的不推薦共享,如果我們的業務邏輯是最大的,我們的 UI 是薄的,或者說我們客戶端訪問數據存儲是薄的,它可以比較快速地實現複用,我們得出了業務邏輯不要去依賴基礎設施的結論。

和上面我們要打磨業務模型的結論也是一致的。

前端狀態管理方案的啓示  

然後把視角拉向前端狀態管理方案,我們研究了大部分的前端框架,一個很有意思的地方吸引力我們,就是除 Vue 等幾個 MVVM 模式的框架外,出現了一些單向數據流的設計理念,Redux 就是這樣一個例子,它可能是因爲要配合 React 使用,而 React 是一個 UI 框架,這個 UI 框架是以不可變爲導向的,前端 JS 借鑑了很多函數式編程的優點,可能更推薦不可變,當然可能是爲了整體 UI 更新的時候更可靠的做一個 Diff 算法,才設計的不可變。

總之前端會在傾向於聲明式 UI 的情況下,更多地去選擇不可變的思路來做 Diff 刷新,這時候我們可能會有一些狀態管理的方案出現,除了 Redux,比如說像 Flutter 有 BLoC 這個東西,像 Vue 有 VueX,都有一些類似的狀態管理方案。

我們從這裏大概瞭解到 UI 是被動的渲染,它不能更改狀態,它只是傳遞了要做什麼的意圖,剩下的事情都不在 UI 層裏操作。很顯然,UI 變成了謙卑對象,這非常有助於寫單測。從第四部分可以知道,我們的架構從 Redux 中汲取了一些經驗。

後端前沿架構的啓示  

後端的架構情況方面,可以發現無論是 6 角形架構,還是 Clean 架構,或者是領域驅動設計的其他相關模式,都是以領域爲核心,領域層裏有一些比如實體類、值對象等,根據建模方式的不同而不同,比如可能用的是四色原型,或者說可能用的是 DCI,也或者其它簡單的一些像 ECB 這種架構模式來建設、劃分領域層的一些對象,但一定是以領域爲核心的,領域裏的類不會再去依賴外邊的類,外邊的類可以去依賴領域內的類,但可能並不會實線依賴。可以發現圖中這個是六角形加上 DDD 的架構,從這裏邊可以看到有一個端口和適配的概念,領域層有一個端口,可以認爲是一個接口,我們有進行適配,這裏適配的就是真正的技術實現相關的東西,然後去實現領域裏邊的接口,使得依賴方向反轉,這樣的好處是我們整個領域模型邊界清晰,完整乾淨,第二個就是我們整個領域模型是完全可測的。基礎設施變更了,對我們來說也可以非常好的平滑過度。這些都在我們的架構中有所體現。

快速開發的思考  

我們再從快速開發的角度看一個問題,比如我們現在有幾十上百號人,如果我們的 Feed 裏有好幾個團隊,不是每個團隊在當前都有任務要做的,這時候我們就希望能夠把這些人都用起來,支援其它團隊的開發,這時候就會涉及到快速支援的問題,比如說我們當時分析了一下這個模型,新入職的同學以及外部的同學跟 Feed 內部的同學到底有什麼差異,對於特別核心的業務,有很大歷史包袱的業務,真的只有 Feed 內部的人才能去做,但是其它支援的同學可以做一些更普適的工作,如果這些工作做得好,也能非常好的發揮相應的價值,最後的結論就是希望能讓中間這部分可獨立擴展,可動態裁減,讓獨立的模塊變大變多、標準化,這樣能讓邊界清晰,開發起來也比較方便,不需要太深入業務認知。這也要求我們打造一個微小的內核,大的可擴展邊界,將更多的時機以標準化的接口方式暴露出去。

組件化  

從公司和整個 APP 層面我們也在做組件化的工作,聚焦在物理組件化層面。但是這裏對於組件的邊界,抽象程度,依賴形式,我們走過比較多的路。

比如說我們有一個殼工程,存在一些業務組件和基礎組件,我們最開始的時候對業務組件的認知是,假如有 A 組件和 B 組件,如果二者之間不想交互,我們就再搞一個 C 組件,讓 A 和 B 都去依賴這個 C,就是所謂的 “下沉”。

後來我們採用的是另外一種方式,把每個組件分成了兩部分,一部分是有一個接口層的模塊,另部分有一個實現層的模塊,接口層之間可以互相依賴,實現層之間不再互相依賴,是通過其他的接口層 IoC 方式進行間接依賴。這樣可以去解決一些類似循環依賴的問題。

這裏拓展一下,比如組件 A 如果想擴展,比如第三方的一個業務想插進一個時機做點事情,我們需要給它增強一個能力,一個配置口子。會有這麼幾種情況,

並且過去的時候,安卓推薦 public 字段,不要使用枚舉等一系列爲性能反設計的內容,,直到現在官方也不太推薦抽象化的技術,但這就導致我們很多數據類都已經做成公開的了,這些繁多的退化類暴露着數據字段,沒辦法通過接口化的方式包裝後透出去,所以不得不又把整個把實現模塊暴露出去。

現在我們希望改變這些做法,它們不利於組件化也不利於大型組織,所以我們選擇用聲明式的 UI ,去 XML 化,並且去減少貧血模型,用接口化的方式去做一些改進。

下圖中,左邊這個圖是整個 APP 當時做組件化的時候劃分的架構層次,這個層次的劃分依據是按照變化的維度去劃分,比如越是與業務相關,那就越容易變化,所以組裝層 /App 工廠在最上層,業務在上層,越往下越通用,越是業務無關。比如說網絡庫,可能沒有必要依賴任何東西,它就是一個很底層的模塊。

當我們按照前面說的後端六邊形架構,DDD 架構,上下之間不能是直接依賴的關係,而是一個反向依賴的關係,比如說網絡庫可能不需要依賴業務,但業務也不應該直接依賴網絡庫,中間我們有適配層模塊,我們依賴這個適配層的 Port,即接口層,適配的接口實現包裝了網絡庫,從而實現依賴反轉,這種做法和思路至少覆蓋主要的,核心的,複雜的,需要測試保證的模塊。

組件我們可以根據集成方式進行劃分,最常見的組件形式是提供某種服務,你需要調用它來獲得這種能力,比如工具類的組件,圖片庫之類的。但也有很多組件是已約束的方式做了某些事,並在很多時機給你留了很多模板方法或者配置參數,希望你通過泛化的方式來擴展和使用,比如框架類組件、提供最小集的中臺類組件經常提供基類。最後像 UI 組件和數據類的這種裸奔的組件,很可能需要去包含它,比如你的業務 View 類裏包含好些個 TextView,比如我們的卡片裏包含一個關注按鈕。

然後我們再加上業務相關性這個維度。可以發現,最右側的業務組件,幾乎不被複用。另一個是注意服務化程度代表接口層提取的容易程度,越偏向使用 / 調用關係的,越容易提取,侵入性也越小。這有助於我們甄別所依賴的組件中某種集成方式的佔比是否健康。

提到健康,也需要知道怎樣判斷組件能力是否完善?對此我們列有一個組件能力模型。組件既有物理組件也有邏輯組件,當然這個約束的主要是邏輯組件。

第一層級,邏輯組件明確對外依賴以及自己提供的服務;第二層級,不僅僅停留在明確,邏輯組件以最小的依賴代價提供相應合理的服務;第三層級,則是在第二級的基礎上,承諾自己的依賴和服務不會輕易發生變更,並在升級後仍較長時間向下兼容這些明確部分。我們可以看下有多少組件到達了哪個層級,還有組件管理上,身份、生命週期、通信機制是否健全、靈活。這些都可以採用評分的方式,最終納入健康度雷達和組件評優平臺。

邊界問題  

組件這塊,有個共建邊界問題,我想分享一個 case,這個 case 也是我們之前遇到過的場景,App 都有 Push 的能力,比方說手機百度 App 裏有內容推送過來,點擊這個推送以後,它直接會進到視頻落地頁中,不會進入到首頁當中,爲了快速啓動跳過了首頁邏輯,這種情況下,我們進入到第二個視頻落地頁,我們有個需求是當返回的時候要創建這個首頁,同時要把剛纔訪問的視頻落地頁的相關條目插入到首頁裏面去,比方說插到第三樓去。我們的想法是它的數據結構和列表的數據結構不一樣,我們需要單獨通過 Server 的其他接口把落地頁內容條目的 ID 傳上去,拿回匹配首頁列表 UI 能解析的這套數據下來,因爲數據的使用者是首頁,落地頁去請求不合理,而如果返回的時候再請求已經晚了,因爲返回的時候就需要使用數據了,還不知道這個東西是什麼,如果再單獨去請求肯定會延遲,延遲會導致一開始插不上這個第三樓,所以我們必須得在每進入一個落地頁就去做請求並緩存到首頁列表能 cover 的範圍內。

這個需求涉及到首頁模塊,也涉及到視頻落地頁模塊,首頁會說我提供了一個通用的能力告訴你怎麼插入,只要按照規則去插就好。視頻落地會說每進到一個視頻落地頁我會按我的通用格式周知,前提是你得先註冊。這個註冊的時機在首頁不好找,需要在 application 啓動時做。這時基礎平臺會說在 application 加一個只有兩個業務方需要的 “定製連接”,會影響 TTI,不會同意。而因爲一些打點的問題,需要區分條目來源於推送還是原始數據請求。

這時候你會發現這個工作沒人願意去做,第一個原則肯定是好的,每個組件都是獨立的,都會提供一些通用的能力,互不依賴,所以應用第二個原則,誰受益誰去做這個事,誰的 PM 去提的需求,誰來做這個髒活,做這個適配。首頁的 PM 提的,首頁的人單獨搞一個模塊去解析視頻落地頁的通知格式,然後拼裝去請求 server。即我們建立了一個膠水組件來做這些不太通用的事情。

剛纔這個例子裏面也有另外一個問題,拿視頻通知格式來說,比如這次是首頁提的,那首頁去做膠水,後面是電商提的,然後電商去做膠水,它們兩個都會去解析同樣的數據,做類似的事情,這是一種重複邏輯的擴散。所以視頻落地頁也做了個膠水層組件,把這部分有重複隱患的代碼收斂起來,便利大家。

這樣你會發現,最後的解決方案可能都是會偏向於我們既有組件,也會有一個類似膠水的組件互相穿插連接。

多產品線方案選型  

還有多產品線的一些方案,比如我們採用的方案是分支的方案,當時的考慮是兩個團隊的發版節奏不一樣,如果想搞通用的組件,去做真實的複用,當幾個團隊之一要做擴展的時候就需要原來做組件的維護者去做一個接口或建設一種能力,一起合作共建。但版本如果匹配不上,這種支援會經常得不到迴應,導致這個需求產生 Delay,所以採用了這種分支方案,也就是對於矩陣內的產品、一個新孵化出的創新 App,可能會直接修改分支去做定製化。只要做好組件化,其實大部分組件都是無需修改就能複用的,只有一部分是有差異的,而有差異的部分我們也有自動化 merge 工具來降低工作量,所以這種方案是可以接受的,尤其在效率上,雖然有一定的概率導致組件的通用性不足。這有點像 DDD 裏 Context Map 中的跟隨者和獨立自主的模式。

當然,插件的方式來實現組件設計的真實複用纔是理想態,但現在看條條大路通羅馬,遺留代碼基太大,沒必要換這種方案,收益不高。

最右邊是構建方案(DDD 裏 Context Map 中的共享內核的模式),這種可以基於 android productFlavor 實現,事實上 KMM、JNI 的外觀上都很類似於這種映射方案。

最後的一個關注點,就是怎麼升級。

架構升級假設  

我們有一個假設,當時想做升級的時候,因爲升級動作比較大,所以考慮什麼樣的升級方式能夠更好的應對不一樣的變化,當時的想法是我們整個業務中有不同的功能單元和能力單元,其複雜度是不一樣的,後面的變化和迭代情況也不一樣的,我們想實現一些細粒度的定製和升級動作,比如說修改其中的一個能力單元進行架構升級時,其它模塊不會有太大的改動,這樣比較平滑,最小化架構升級和業務迭代並行導致多套並存的混亂程度。另外一種思路是做一個大一統的標準化架構,並且推行這個架構。但這種架構對很多不常迭代的業務來說比較複雜,猶如大炮打蚊子。所以最後我們還是決定用細粒度的方式做升級,去做組件化和架構模式的應用。只要保留在想升級更重量級架構時能夠升級這個可選項。這樣下來,整個工程上,有不同量級的可升級的架構,很像一個梯田。

遷移和升級準備  

我們之前做中臺化的時候也做過一個升級的動作,但最後失敗了,原因是我們有一個非常複雜的列表,其中有很多各種刷新,各種實踐,各種分支邏輯,比如錨點邏輯,搜索和插入,等非常複雜的邏輯在裏面,但我們當時做中臺的時候希望中臺是通用的,不需要那麼複雜,所以當時就另起爐竈去搞一個乾淨無包袱的組件,設想了三層輸出,就是最小可以有什麼,通用級可以有什麼,業務飽和級列表可以有什麼,業務飽和就是帶上 Server 的協議和策略。然後在應用這個中臺組件時,比如說我們當時在做付費項目時,發現完全是另外一套策略,業務飽和級成了累贅,通用級爲了通用,非常的薄,我們需要填補很大的空白邏輯。另外,由於它是獨立出來開發的,很難再反哺到一開始所說的真正複雜的列表中去,導致升級中斷。

這次升級,不僅注意到這些,而且我們針對架構升級影響到的那些團隊做了很多培訓和推廣,來應對架構升級中的一些設計問題。

有一些人會覺得引進新的時髦的東西就是好的,比如 Android 規範推了這個,又推了那個,他會不亦樂乎的追隨和集成。再比如這次 Android 的架構導向偏 MVVM,但是,他發現 Redux 不錯,所以在 MVVM 裏再攢一個類似 Redux 的設計模式。我們覺得這不太合理,而且大家對這些會有一些自己的理解,會產生歧義。再比如說 MVC 就非常普適,但我發現前端、Android、iOS,不同工齡背景的人,沒有多少能達成一致的見解。

所以,這次,如剛剛介紹的思考和原則,又是前端,又是後端,雖然我們也是引入,但重新定義出了一套獨有的概念,並予以規範化的解釋,使之出處一致,避免扯皮這些。比如我們有個 Processor 的概念,有點像 Redux 的 Producer,但是不直接引用這個名字,因爲有差異。

若干場景的架構示例

上一 part 我們集中描述了思考,具體落地只是順理成章的事,現在舉幾個場景來做下示例。如下圖,這裏我採用了 C2 描述語言去描述,不採用 C4 或者 4+1。

我們的組件裏 Processor 承擔業務處理的邏輯單元,Ability 作爲基礎設施能力的抽象單元來參與 Processor 裏的業務邏輯,比如說我們的 UI 控件,或者幾個 UI 控件被包裝成 Ability 使用。

再說說 Extension 概念,比如一個處理加載相關邏輯的 Processor,在加載失敗時,可能不同頻道有不同的微調處理,那麼這個加載失敗可以定義爲擴展點,由不同的頻道注入 Extension 來解決。是的,我們的 Processor 基本上所有的方法都是私有的,也就是雖然可以泛化,但是以 Extension 組合的方式來表達擴展。注意,這裏的擴展點是面向實現的。也就是說強依賴剛剛說的 Processor。如果是面向接口協議的,一些通用的時機,我們的選擇是發 Action,我們馬上介紹。

上面說的組件,需要通過連接件組合組裝起來,連接件也要作爲容器處理組件間,父子容器間的交互訴求。比如我們的展示頁面,用 Page 接口抽象,Page 組合管理很多 Ability 。ProcessContext 作爲 Processor 的容器會去處理很多 Processor 間的交互邏輯,也會把父 ProcessContext 的 Action 轉派給 Processor, 比如通過 Action 的方式去發消息給 Processor,Processor 也可以使用另一個 Processor 的能力,但需要以 Assistant 接口包裝。一般來說,解決通信,提供消息機制、服務調用機制、共享內存機制三者中的一個就夠了,所以最後一種我們沒提供。

我們定義了很多約束原則,這些原則都是事先就規範化的。鑑於篇幅,不再敘述。

說到 Action,我們不得不再提下 Redux。只是這次以子系統視角。下圖這是列表子系統,Feed 還有視頻落地頁子系統、圖文落地頁子系統等。每個子系統都是獨立的,我們不希望這個系統被污染,我們是按照系統思考的方式去做,拿這個例子來說,我們的系統是 Action 應激,沒有其他的方式使用這個系統。而且我們擺脫安卓原來的低級事件,比方說安卓點擊這個東西,彈什麼東西,我們把點擊這種低級事件已經轉化成了一種高級事件,雖然名字可能叫 Click 事件。這樣我們可以基於 Action 的錄製和回放來實現自動化測試。尤其在應對複雜的業務時,實現邏輯的自動化校驗會很有幫助。Redux 有很多的優勢,但我們最看重的是事件溯源,CQRS+ 單向數據流的好處。另外這些也有助於非脆弱性單測的編寫。

架構升級的過程中,我們把機制作爲框架下沉,包括編排引擎、插件打包。見下圖

我們複雜的業務不是很多,但也有一些,比如刷新,圖文落地頁,對於這種複雜的業務,我們傾向於引入 DDD 戰術來改造,比如把真正的實現從外部注入進來,注入進來以後我們會走到一個比較乾淨的應用層和領域層裏邊去,這個應用層的目的是去接收外部的 Action,比如搜索(和 Feed 平行,未使用這套架構組件)過來一個事件通知,會通過 event bus 發過來,在應用層會被轉成 Action 再進來,我們會在概念上維護 PM 提需時的用例,這些用例會去配置策略參數,然後進一步傳遞領域層進行更改,比如說我們的顯示列表層就是一個領域層聚合,它以數據的方式完全代表最終的顯示狀態。我們可能還有一些還原歷史的訴求,歷史是單獨的一個概念,也是 PM 和各方面都能認可的通用領域語言概念,這是領域層的一些實例。數據處理這塊我們採用 Repository,不是 Android 的 Repository,而是 DDD 裏的概念,Repository 和 DAO 最大的不同技術,Repository 是以集合的方式而不是以數據庫語言或者網絡協議請求方式來維護聚合,Repository 的接口層在領域層,實現在基礎設施層,基於前面說的端口和適配器的概念,實現依賴反轉。

   展示層我們應用了三種模式:

  1. 第一種模式是應用了邏輯圖層概念,這不僅僅是 Z 軸,而且體現在兩個無真實 UI 視圖的層,比如手勢層,誰在上誰先消費或過濾,這對於事件消費的管理有幫助,另外某些 UI 控件想同顯同隱,可以放在一個層上來簡化管理。

  2. 第二種模式是將頁面分區分成命名卡片槽位的方式,這種方式可以應對局部的組件化,比如說我們這個運營槽位可以作爲獨立的一個組件使用,供別的頻道更細粒度的進行復用。

  3. 第三種模式是 UI 技術實現,比如我們說列表過去依賴了 ListView,現在依賴 RecyclerView 這個特定控件,未來我們使用了 Jetpack Compose 來實現列表,他們侷限於某種技術細節和開發方式,我們把這層實現和要提供的接口能力隔離開來,接口可以使用 Ability 架構組件,真正的實現在業務組裝配置的工廠裏注入進來。這裏的 UI Ability 不同於把頁面分區,也不同於一個 UI 控件包裝成一個 Ability。比如都是用 RecyclerView 作爲實現載體,但是我們的 Adapter Ability, Footer Ability 和 Update Ability 都需要它。

最後,這三種模式可以進行組合嵌套,你中有我,我中有你。比如我們先用邏輯圖層,然後它的某一層,可能嵌套使用分區命名卡片槽位,槽位裏邊也可以嵌套配置圖層,或者將某個槽位組件做成 Ability 或 Ability 的容器 Page。也可能是反過來,比如整個 Page 裏很多的 Ability,其中的某個 Ability 的載體是列表控件,列表條目是卡片槽位,某個槽位進行了圖層化處理。

當然這需要 Ability 設計時考慮它的複用方式,比如是否支持槽位,如果可以支持,就將槽位管理器引入進來,然後由工廠注入時配置開關。

架構維護控制

在架構維護控制方面,我當時看了《演進式架構》,很受啓發,演進式架構的一個道理是想利用適用度函數儘可能管理和降低架構裂化程度,我們也定義了一攬子適用度函數。比方說像 McCable 圈複雜度,穩定性度量等。

有一次我們去參加 ATAM 的培訓,它是一種架構權衡的分析方法,類似以評審會的方式對質量屬性進行評價,進而對架構進行評價。也討論了哪種方式可以作爲長期的適用度函數,但總體上這種方式開展的不頻繁,和團隊對沖突管理的好壞有關係。有一段時間,我們也研究了 Google 提供的 GSM 這種方式,瞭解到除了定量還可以通過觀察開發者是不是產生了某種行爲來衡量收益和結果。這種方式的實施仍和管理者理想中對於目標的掌控方式有很大關係。之前也提過,如果管理者的想法是讓每個人都忙起來,工作飽和起來,被管理者可能對提效這件事不再敏感,因爲無事可做,仍要假裝很忙到 11-12 點,那還不如慢慢做。我聽過一個故事,說老闆希望員工很忙,後來他做到了,以至於有顧客進店都沒人去理他,因爲大家真的忙起來了。

除此之外,不同的團隊,比如不同階段,不同環境和文化,不同規模,不同業務形態,在架構的選型上,以上的架構實踐方式是否適合,需要自己權衡。

可以做一個架構能力評估模型來評估下,自己當時的架構是什麼水平的,可以做成什麼水平,平臺支撐是不是足夠好。比方服務端能不能做微服務?並不是每一個團隊都適合做微服務。比如我們當時非常關心的一點是,是不是有一個最佳的決策路徑,我們的一線開發者遇到問題時,可以助力設計決策。其他部分詳見下圖。

另外我們需要做好防劣化,這塊有幾點印象比較深刻,比如說像 Emacs 編輯器可以煮咖啡嗎?也許能,也許不能,我對此的感受是,程序員希望做一件事時,沒有其他的干擾,比如編碼時,他不希望又從瀏覽器收藏夾的某個鏈接進入網站進行編輯搜索文檔。最好不要離開 IDE,不要離開開發者手邊的東西,離它越近的東西越有價值,比如我們定義一個編碼規範,在 IDE 裏直接提示應該怎樣做,開發者可能懶的去找規範,而不是提交完了,再氣沖沖的去改問題。

第二點就是我們會在很多地方做埋點去監控東西,比如說靜態內部類是單例是最好的寫法嗎?不是,最好的寫法是,你只要去標一個註解說這是單例,不管裏邊怎麼實現的,是不是真的單例都沒關係,就可以在團隊級別起作用,大家得以自覺的不再去創造新的實例了,怎麼能讓這些契約成爲武器,那就讓他們儘可能在埋點的地方,進行自動生成。

第三點,我們的鉤子裏也有助於我們知道有哪些單例,有哪些架構組件,有哪些卡片等等,這些數據統計會對做架構決策有幫助。

下面這是我們的幾個示例,這幾個東西都是 IDE 上的,都是基於開源框架去改的,它有各種衡量,比如像複雜度的衡量和抽樣度的衡量,架構決策上是引導,lint 檢查。我們自己在做 IDE 插件,我們的模板卡片怎麼統計,它到底是什麼卡片,什麼類型,它的數據結構是什麼樣的,大概長的什麼樣子,能不能複用,這些東西都可以在 IDE 的工具裏找到。

還有就是平臺,我們基礎平臺團隊在 DevOps 平臺幫我們檢查很多指標,像組件接口的依賴化的程度是不是合適的。後面這部分我們還在整合,不方便展示,包括健康度雷達,組件的健康度指標等。

作爲一個業務開發團隊,要體現業務價值,所以我們的業務目標是業務數據,比如 DAU、時長、留存、轉化率,因業務形態、App 形態而異。這是必須直面的部分。

但這些業務數據往往不是一個業務開發團隊能左右的,且不說裏面有天時地利,而且需要 PM,策略,開發,市場等一衆團隊合力解決的。

所以另一個間接目標可能會是這個業務開發團隊的口碑,一種公司內的 “品牌”。這意味着雖然數據指標仍不如意,但我們已經盡最大的努力做到做好,比如我們做的需求數同比環比提高了一個量級,或者比公司其他的相似團隊有質的提升。因爲後者對開發團隊本身更可控些。

有了這種用研發效率間接體現業務價值的思路,我們可以從需求數量(多)和需求質量(好)上着手繼續拆解。

需求數量,可以用一個版本需求吞吐量衡量。那爲什麼需求可以突然做得多?可以大體拆成需求,人,效率三因素來看:

也許是 1)需求拆的小或者需求簡單;

也許是 2)大家加班肝出來或者人員給力水平卓越;

也許是 3)效率提升,命中了之前做的改進措施;

那相反,爲什麼做得慢,需求複雜或者人員躺平或者沒命中改進措施。很顯然,需求拆小可能是 “考覈什麼就會得到什麼” 的古德哈特定律對團隊的反噬。也可能不是,我們可以把需求類型拆解爲:

從這裏也可以看出,提煉質量屬性以及多維度指標是可行且可刻畫需求本身(因爲下面說的命中和此有關)。

看起來效率提升是可持續發展的正確思路。但拿技術手段來說,這裏都有命中和沒命中之說。比如改進措施:

1)架構邊界清晰、可協作分工,設計通用、機制健全、可高度複用(用質量屬性度量)。      因爲我們不是在白紙上工作,都是在一個大的遺留代碼中進行改進,沒個幾年不可能翻新一      遍代碼,也無法保證翻新的收益以及翻新過程中間新出現的負收益。所以那些只談從 20 天變成 5 天的,不談命中概率的,我是持懷疑態度。

技術上的改進措施還可能是:

2)採用了自動化或者低代碼技術(自動化率衡量)

3)動態化技術使雙端需求變成了單端需求,比如用了 H5、RN、KMM 等技術(隨版轉非隨率,人力投入比衡量)。

後面兩者(2、3)依然存在命中概率,所以做得省、做得快不代表需求做得多,因爲可能把人投資在了建設和維護這些如低代碼平臺,動態化能力完善上,改善架構和設計上。儘管如此,相比非技術手段的改進措施,如流程得到優化;梯隊合理、培養了 backup,導致不需要有人從旁指導,接手速度快;等等吧,技術手段還是更研發可控些。

需求質量,那肯定業務數據提升代表需求質量好,數據提升的前提是:

1)體驗好;2)線上穩故障少;3)我們的數據指標統計準。

雖然後者無法覆蓋前者,但在一定程度上能間接刻畫。而這些間接指標我們有些是定性,比如說團隊成員有了數據驅動意識,有些可以拆解到研發可控的質量屬性上:

1)性能;2)可維護性、可用性(魯棒性);3)可維護性,可預測性(平臺監控、工具檢查、測試覆蓋等)。

說了這麼多,就是想說我們可以通過建設健康度雷達來刻畫質量屬性,從而可以間接量化架構優化和重構升級的收益,這是一種不夠嚴謹但研發可控的方式。但量化的收益是需要考慮命中率的。可以使用工具來識別迭代熱點等來提高命中率,雖然如股票一樣,這些指標仍代表的是過去,但對趨勢研判是有助力的。另外就是架構、基建、機制這種往往是每塊業務都避不開的,天然能命中。健康度雷達的另一個好處,我之前有看過,說這些質量屬性上的提升,也會提升開發者的安全感和幸福感。

回顧架構過程,我們做架構升級需求分析的時候,並行開發快速支持其它業務、業務動態下發、儘可能免測來提高研發效率、提高質量是我們的需求。我們甄別了幾個除性能之外的質量屬性,我把可觀測性加在裏邊去了,我們很多東西是爲了它去做設計的,然後我們進行了架構風格的選擇,我們可以從內部自下而上進行改造,但是我們也想盡量和行業接軌,推行了很多風格的模型去做選擇和借鑑。

最後就是維護的幾個指標和平臺建設的情況,我們整體獲得了一些收益,比方說像輸出複用的收益,還有並行開發、動態化這方面得到的一些改善,測試體系也得到了一些保障。很多時候在真實的複用場景中,如果複用的東西出現問題,影響面會非常廣,我們希望有一個測試的體系來保障這些是可以改的,如果沒有,有的時候確實還不如拷貝的方式,因爲不希望影響到別的業務,所以想要真實複用,我們必須要建設測試體系。

開發效率的提升來自於免測的情況,還有一個是非單點依賴,我們原來的業務很複雜,非常依賴某幾個人,現在有了測試可以很好地應對這個問題,通過測試來知道改動對某些 case 是否有影響,這樣我們就有信心去重構。

在決策自由度的改善上,比如從業務維度進行隔離後,各個業務參更好地根據自己的特點進行決策,因爲它的粒度更細了,無論怎麼說,細粒度化和積木化確實是大型 APP 的走向。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/2aKLFeyqjsvOZvi5Nc1mzQ