2018,咱們的組件化實施之路 | 掘金年度徵文

前言:本篇文章是諸葛找房 iOS 技術團隊近半年組件化實施之路的經驗積累與沉澱,近半年來咱們的組件化經歷了從0到1的質變,開發方式由原有的多人混合開發逐漸變爲了相對獨立的分業務線開發,而且已經初步實現了同一組件在不一樣項目下的快速集成,組件化帶來的便捷與迅速正在慢慢地向咱們鋪展開來,後續充滿想象空間。然而對一個已經在線上運營多年的系統進行組件化,並非一條容易的路,這一路咱們升級打怪,聚集全組人的智慧,及時的調整組件化結構,讓它不至於走彎路。今天咱們將主要從工程實施的方方面面與你們分享一點咱們的看法,文章較長,建議你們先收藏哈。git

首先簡單看下關於組件化選用哪一種方案和組件化分層的問題

  • 目前比較流行的大體有3種,RouterProtocolTarget-Action.咱們採用了第三種,在此要感謝casa前輩的智慧與無私貢獻。至於選用哪種,不在今天的討論範圍內,由於不管你打算或者正在使用哪種,與今天咱們要講的都沒有衝突。
  • 組件化通常分3層,從下至上依次是基礎組件、基礎業務組件和業務組件。其中下層不依賴於上層,下層的實現對上層是透明的,上層使用下層提供的服務和接口而沒必要關心其實現細節,下層不可隨意更改對外的接口,位於同一層的各個實體之間經過協議進行通信。讀者能夠經過諸葛 iOS 技術演進組件化來了解咱們的組件化總體架構設計。

1、從工程的角度,如何看待組件化?

將一個工程組件化,就像是從新建造一個結構設計不合理的大樓同樣,這個大樓的各類線路、各類管道都雜糅在一塊兒,承重牆和傢俱東倒西歪,雖然能提供正常的居住服務,可是後續對大樓的的改造與裝修卻很費事。爲了之後能容納更多人居住、提供更好的居住體驗,有必要在如今對大樓進行重建。在改造的過程當中,仍然須要提供正常的居住服務,所以大樓不能採用爆破的方式徹底推到重建,由於那樣的成本太高,只能在每一次版本迭代中進行組件化,組件化對用戶和市場來講是無感知的纔對。改造的時候是一個房間一個房間的改造,須要進行房屋的物品歸類、線路拆分、垃圾傾倒等各類準備工做,而後就是把全部相關東西都挪出去進行單獨改造,挪出去的時候要保證整棟大樓不會倒塌,其餘的功能不受影響才能夠,挪出去的東西要組裝成一間功能獨立的屋子,這個屋子就是一個小的生態系統,後續的維修與改造都只須要care這個屋子就能夠;建好這個屋子後,須要再把它放回原處,這間屋子就能夠正常住人了。跨域

2、業務組件拆分的幾個步驟

一、組件預處理bash

預處理須要在主工程中進行,預處理的主要目的是爲了給第二步組件從主工程抽離達到單獨運行鋪平道路,因爲預處理髮生在主工程中,所以預處理階段不須要考慮代碼同步的問題,預處理主要包括如下幾個方面:網絡

  • 在主工程中對該組件的全部相關控制器跳轉和服務調用進行引用解耦,若是採用的target-action方案,就都經過mediator進行頁面跳轉和服務調用,但組件內部能夠沒必要採用這種方式。相似於拆承重牆以前,先從別的地方運一些足夠結實的柱子過來,支撐在原來的地方,保證屋子拆出去後,大樓不會發生倒塌。
  • 在主工程中將該組件涉及到的文件引用關係梳理清楚,去掉沒有用的引用,同時對該組件按照統一模板建立對應文件夾。這裏的統一模板基本上跟咱們主工程的文件結構一致,都有本身獨立的網絡層、存儲層等。總之須要把要拆出來的組件當作一個獨立的項目來看待,一個項目須要有什麼,這個組件就須要有什麼。
  • 按照模板將該組件的全部文件進行從新歸類,在這過程當中須要區分哪些文件屬於公共的須要下沉的,哪些是這個組件獨有的。所以這個過程會不斷豐富完善公共的基礎業務組件。

二、組件抽離、編譯、運行架構

  • 首先能夠經過pod lib create XXX命令建立一個pod工程, 而後配置該組件的podspec文件,指明該組件都須要依賴哪些庫。
  • 將通過預處理的的組件抽出來放到Development Pods中的class中,將圖片資源放到Asset中。
  • 給該業務組件設置開發環境變量開關,更改組件中本地資源如圖片、plist等的加載方式,設置組件的程序入口等
  • 若是該組件跟別的業務組件有通信,那麼還須要提供別的業務組件的接口。若是此時別的業務尚未拆成組件,那麼建議創建一個接口組件,專門存放那些尚未抽成組件的業務接口和服務。

三、組件引入工具

  • 第二步結束,且組件經過初步測試後,就能夠將組件以開發庫的方式引入主工程了,此時該業務組件不須要進行發版。關於開發庫的引入方式,下面會再進行介紹。

以上大體的介紹了業務組件拆分的三個大階段,接下來咱們再看下組件拆分過程當中遇到的一些具體問題,這些問題相信你們在實踐中基本都會遇到。

1、主工程中業務組件的引用方式問題

注:業務組件能夠沒必要進行pod發版,一是由於業務組件開發中頻繁的發版很耗時間,二是業務組件擁有本身的tag,沒必要經過pod版本號進行控制。能夠直接在主工程的podfile中以開發庫的方式引入業務組件,開發庫通常有兩種方式,分別是pathcommitId方式。組件化

  • path方式

    • 簡介:path方式是指業務組件在主工程中以路徑的方式引入,這個路徑能夠是相對的也能夠是絕對的,通常選用相對路徑,即將全部的組件與主工程都放在一個文件夾下,這樣在主工程中就能夠用下面的方式引入業務組件。
      • pod 'ZGAModule', :path=>'../ZGAModule/'
    • 優勢:開發庫與遠端是同步的,組件能夠直接在主工程裏改,改動後分別在組件裏提交便可。
    • 缺點:全部的開發人員電腦上必須用一套路徑一致的文件夾,想要把主工程運行起來的話,須要將全部的開發庫都下載下來,不然當別人引用了一個我電腦上沒有的組件的時候,我這裏就會報錯;update主工程的時候,必須依次更新全部的開發庫,可能會有遺漏;若是使用了Jenkins打包,會污染Jenkins環境。
  • commitId方式

    • 簡介:commitId方式要求開發庫在主工程中以該組件遠端倉庫地址的方式引入,能夠指定commitId,也能夠不指定。不指定commitId的話,就默認始終指向最新的業務代碼。
      • 指定commitId:pod 'ZGAModule', :git => 'git@git.zhuge.com:iOS/ZGAModule.git', :commit => '1234567'
      • 不指定commitIdpod 'ZGAModule',:git => 'git@git.zhuge.com:iOS/ZGAModule.git'或者pod 'ZGAModule',:git => 'git@git.zhuge.com:iOS/ZGAModule.git', :branch => 'dev'
    • 優勢:開發人員本地不須要維護一套路徑一致的工程文件,想要運行主工程,直接pod install安裝各個組件便可;不侵入Jenkins的環境;
    • 缺點:開發庫與遠端不能同步,組件有改動的話,只能在組件裏改,不能直接在主工程裏改;若是沒有指定commitId,那麼組件更新後,主工程只能用pod update的方式更新該組件,速度較慢;若是指定了commitId,那麼每次改動組件後都獲得主工程的podfile中,修改該組件的commitId;
  • 咱們採用的方案

    • 以上兩種方案各有優缺點,咱們綜合了這兩種方式的優勢,咱們總體上採用了第二種方案,但沒有指定具體的commitId,這樣就會始終指向最新的業務組件;但podfile中全部的業務組件都有一份以path方式引入的備份,只不過在遠端這些path引用是被註釋掉的。之因此有這些path備份,是爲了方便開發人員在本地自由切換,最大限度的提高開發效率。
    • 針對小的改動,開發人員能夠在主工程中手動將commitId方式改爲path方式,調試經過後,再將podfile改回去,而後直接提交對應組件的改動。
    • 針對大的改動,建議直接在組件中進行開發與提交,最後在主工程中update該組件便可。
    • 其餘方式:對應的能夠經過腳本方式進行組件化工程的installcommitupdate等一系列操做,或者經過腳本進行兩個不一樣repo之間的merge操做,但這樣作都須要再開發維護一套腳本,致使系統複雜性變高。
  • 附:主工程podfile的大體寫法

#******************** 諸葛私有庫 ********************
  #遠端庫
  #A業務
  pod 'ZGAModule',:git => 'git@git.zhuge.com:iOS/ZGAModule.git'
  pod 'ZGBModule',:git => 'git@git.zhuge.com:iOS/ZGBModule.git'
  pod 'ZGCModule',:git => 'git@git.zhuge.com:iOS/ZGCModule.git'

  # 本地庫(遠端這些本地庫方式必定是被註釋掉的,review的時候若是發現被打開了,那麼能夠將本次提交打回去,或者開發相關腳本進行監測)
  #A業務
    # pod 'ZGAModule', :path=>'../ZGAModule/'
    # pod 'ZGBModule', :path=>'../ZGBModule/'
    # pod 'ZGCModule', :path=>'../ZGCModule/'

複製代碼

2、業務組件的開發時機問題

這裏咱們主要分享下如何在人員有限、需求不斷地狀況下處理好組件化與版本迭代之間的時間衝突。post

一、需求開發時間佔比和兩個生命週期

首先咱們提出一個簡單的概念,這個概念你們一聽便知:需求開發時間佔比,也就是版本需求開發時間佔當天有效開發總時間的比例,並給出高(80%)中(50%-80%)低(50%如下)三個時間檔。假設咱們能夠在版本迭代過程當中需求開發時間佔比爲的全部時間段進行組件化開發。測試

下面咱們再看下版本迭代的大體生命週期,不一樣公司可能稍微有些差別,但大體是類似的。spa

版本生命週期-開發時間佔比

一個業務組件的通常生命週期以下 :

組件化生命週期

二、組件的開發時機

組件的開發時機要避開需求開發時間佔比較高的時間段,從上圖中能夠看出,組件的開發時間主要是如下兩個時間階段:

  • 版本上線以後至版本新需求開始研發以前
  • 版本總體提測後至版本灰度測試或者臨近上線前

三、 組件生命週期不一樣階段的時間原則

  • 組件的預處理階段,須要在主工程中進行,不用考慮代碼不一樣步問題,而且預處理工做bug率最低,在版本生命週期的任何階段均可以進行
  • 組件預處理階段結束後,須要看版本總體時間是否充分,不充分的話不要把組件從主工程中抽離出來,由於組件相關的代碼一旦從主工程中抽離出來,組件化的生命週期就必定要在當前版本的生命週期內,而不能拖延到下一個,不然的話須要處理不少代碼不一樣步的問題。所以預處理工做結束後,若是時間不夠充分,那就在下個版本,把該組件從主工程中抽離出來。
  • 新的業務組件不須要考慮預處理和原有代碼的抽離工做,直接在當前版本迭代中開發便可

3、業務組件測試

爲了保證線上服務的穩定性,須要對新抽離的組件進行多輪測試,尤爲是組件代碼跟主工程代碼不一樣步的狀況下,更要增強測試。能夠採用三級測試的方式進行測試:

  • 三級測試

    • 組件單元提測(組件開發好後須要該組件的開發人員對組件進行自測)
    • 組件主工程提測 (自測沒有大問題後,能夠通知開發過該業務的人須要進行一輪測試)
    • 版本提測(依賴測試人員進行迴歸測試)
  • 測試原則:

    • 功能一致:與線上版本的功能徹底一致
    • 代碼一致性:分模塊分頁面一行一行的捋代碼

4、業務組件化分支合併到matser

  • 通常的,組件化分支是單獨的一個分支,因爲組件化工程與master主工程差距較大,不少文件被移動,進行代碼合併時必然會產生不少沒必要要的衝突,所以組件化工程測試完畢後,若是不想解決那些衝突,能夠不採起合併到master的方式,而是直接替換master上的工程
  • 業務開發直接在當前的組件化分支中開發,再也不從master遷出
  • 等組件化基本抽離完成後,再回到正常的開發方式中

5、組件後續的開發與維護和組件回滾

  • 關於組件後續的開發與維護

    • 原則上只須要在各自組件工程中進行開發,不須要依賴主工程。
    • 要保證組件的基礎生態環境與主工程的基礎生態環境一致;
    • 組件對外暴露的接口一旦成型後,後續不能夠輕易改動,可是能夠增長新接口。
    • 組件改動後,須要通知主工程的負責人,更新該組件,目前沒有主動通知的機制,後續須要進行完善。
    • 組件後續的開發遵循正常的git開發準則和gerrit代碼review準則
  • 關於組件回滾

    • 因爲各個組件都是一個個獨立的git項目,目前能夠作到針對某一個業務進行代碼回滾,而沒必要主工程總體回滾,回滾時直接在主工程中從新指定commitId便可

6、業務組件的粒度(由大變小)

  • 工具性之外的業務組件,能夠不遵循子庫的設計方式,一是它自己已經很大,都在一個工程裏開發容易產生工程文件衝突;二是業務之間的引用關係由外界決定,沒法確定兩個業務子庫之間不會產生引用,即使經過mediator引用,也不太好,不如拆成一個一個的小組件

7、關於業務組件中文件的命名規則

  • 不一樣項目中引入同一業務組件時,爲了下降組件對主工程的侵入性。最好對組件中的類進行重命名,能夠採用組件名+類名的方式命名。如新房業務組件下的新房列表,能夠叫作ZGNewHouseModuleNewHouselistVC

8、關於不一樣項目下同一業務組件的個性化差別解決方案

關於組件的個性化解決方案,目前可供咱們選擇的主要有兩種,一個是在組件內部經過環境變量來區分不一樣端,另外一個是經過git的分支進行管理。

兩種方案各有優缺點,至於選用哪種方案必須從業務當前的類似性業務以後的發展趨勢(只是同步現有的代碼仍是同步之後全部的代碼、產品之間的差別)代碼基礎環境類似性代碼複雜度開發人力等幾個方面綜合考慮,具體分析,不能盲目的選擇。 簡單看下這兩種方案:

關於第一種方案:

組件須要對外暴露一個設置當前App類型的接口,組件保存該類型後,開發者須要在組件內部有區別的地方經過該App類型進行區分,來展現不一樣的視圖、提供不一樣的功能與服務。

優勢:

  • 組件只須要有一個分支,各個項目能夠用一個地址引用該組件,便於管理。
  • 組件修改提交後,各個業務線都會有更新,只須要改一次便可。

缺點:

  • 每次有新的App,都須要從新定義一個新的App類型,會讓組件內部的代碼複雜度變高,後續維護成本變高,對新人不友好。
  • 修改一個應用的個性化需求後,存在污染其餘應用的可能性。
  • 組件的基礎環境可能跟不一樣項目的基礎環境不一致。如A項目要求組件使用1.1版本的三方庫,B項目要求組件使用最新的三方庫,因爲沒法簡單的強制不一樣端的基礎環境一致,這種方式會引發庫衝突。
  • 人力分配比較模糊。

關於第二種方案:

業務組件爲每個應用都創建對應的分支,以C端的新房組件爲例,master分支爲C端的新房,經紀人端的新房從master分出,能夠叫作newhouse_agent分支,開發人員在各個分支中進行組件化的差別性開發。共性的東西由master的維護者開發。從現有的C端實際狀況來看的話,基本不存在個性化分支合併到master的狀況,由於經紀人業務不太可能面向C端用戶,只存在master往別的分支合併的狀況,經過git的代碼合併來同步共性業務。

優勢:

  • 不一樣端的個性化需求經過git多分支進行管理,組件內部,不須要定義App類型,複雜度低,維護成本低。
  • 修改一個應用的個性化需求後,不存在污染其餘應用的狀況,由於彼此之間保持獨立。
  • 組件在不一樣應用下的的基礎環境能夠不一致,針對不一樣項目能夠有不一樣的三方庫版本,甚至不一樣的三方庫依賴,靈活性較高。
  • 共性業務由master的開發者、通常是組件的建立者維護。個性化需求由對應端的開發者維護,分工明確。原則上容許master合併到其餘分支,不容許其餘分支合併到master,其餘分支和分支之間能夠根據業務需求有選擇性的合併。共性業務一樣只須要修改一次便可,原則上master上是個性化最少的組件。

缺點:

  • 一個組件可能會有多個分支,若是用的不是master分支,那麼就必須指定版本號才能夠。
  • 組建依賴的相關基礎組件可能都須要有調整。
  • 合併過程當中可能出現衝突以及一些不須要的功能,所以對於小的同步,建議直接手動複製、粘貼修改,大的同步能夠用merge操做,而後進行微調,該操做須要進行估時,歸入排期。

流程以下圖所示:

git 多分支管理
綜合分析後,咱們總體採用 git分支、局部採用環境變量的方式進行管理。

9、其餘細節問題

  • 如何使用腳本提高效率,咱們開發或規劃了哪些腳本?

    • zg_pod_upload:用於簡化pod庫的發版流程,同時支持組件的本地校驗。
    • zg_pod_initialize:用於快速建立一個組件化工程,合併了pod lib create的幾個命令,並在對應的class文件下,建立對應的組件模板。
    • zg_file_filter:用於組件引入時的文件去重。
    • zg_file_replace:文件重命名腳本,用於批量的對業務組件的相關代碼從新命名。
  • 如何處理每一個業務組件的三方庫初始化?

    • 每一個組件負責本身三方庫的初始化,全部的三方庫都在各自的業務線中進行初始化,在主工程殼子中調用各個業務組件對外暴露的SDK初始化方法便可。
    • 若是三方庫依賴於bundleID,那麼須要爲對應的bundleID申請對應的三方庫配置ID和Key
  • 如何處理組件之間的相互跳轉?

    • 現有的Mediator方案自己就支持帶參數、不帶參數、帶返回值、無返回值、帶block回調、不帶block回調的調用。具體可閱讀casa的系列文章。
  • 業務組件之間如何進行復用?

    • 複用通常分爲UI複用、Model複用、數據與服務複用
    • 原則上不提倡經過接口方式進行業務UIModel的複用,若是就是想複用,能夠把對應的UIModel下沉到Base層以後再複用。
    • 數據與服務能夠經過業務接口的形式複用。
  • 業務組件的接口是否須要與業務代碼拆開?

    • 業務組件接口與業務代碼放到一塊兒太容易發生跨域訪問,後續維護問題多,由於開發人員可能爲了圖省事,不經過業務接口進行通信,而直接引入了具體的類文件。
    • 能夠把業務組件的接口作成業務組件的子庫,而且約定規範,業務調用時只容許使用該業務組件的接口組件,不容許直接使用業務代碼組件。
    • 或者單獨創建一個倉庫,裏面存放全部的業務接口。
  • 如何處理業務組件的圖片資源歸屬問題 ?

    • 業務組件本身維護本身的全部圖片,不須要把全部的圖片資源都放到Base層,以避免打散後續開發的連貫性。
    • 容許不一樣業務線之間有重複的圖片
    • 不須要將重複的圖片單獨搞成pod庫
  • 組件之間出現雙向引用或者主庫下的子庫之間出現橫向依賴怎麼辦?

    • 同一層的組件實體之間經過協議進行解耦,須要避免出現這種狀況
  • 如何讓分離出來的代碼與主工程保持同步?

    • 總原則是推遲業務組件從主工程中分離出來的時間點
    • 儘量的在預處理階段作更多的事情
    • 分離出來後,就須要作好代碼修改記錄,以後手工進行同步了,這時候應該快速的把它作成pod庫,以後就以組件化方式開發
    • 分模塊、分功能進行全鏈路的迴歸測試
  • 如何處理域名與多target問題?

    • 第一種是在各自的組件中本身維護本身的域名,分散管理,缺點是會寫一些邏輯類似的代碼,每一個組件都須要進行環境配置,可能會拖慢啓動速度
    • 第二種是將域名寫在baseModule裏,統一管理,將target的邏輯也寫在baseModule
  • 若是組件A有一部分邏輯沒有徹底從主工程中抽離出來,或者組件A引用了尚未拆出來的組件B,這時候該怎麼辦?(半組件化)

    • 針對第一種狀況以將未徹底抽離出來的邏輯寫在主工程中A對應的target裏,在A中經過Mediator進行調用,後續再進行不斷地拆分
    • 針對第二種狀況能夠將組件B的接口寫到CommonModuleExports中,組件B的target依然放到主工程中,在A中以Mediator的方式引用B組件,後續再對B組件進行拆分
  • 如何管理通知?

    • 組件內部能夠正常使用通知
    • 跨組件的通知須要慎重,儘可能不要習慣性的採用通知。

10、關於業務組件的評價標準

標準老是要有的,有了標準,纔會有前進的方向。然而目前關於如何評價一個業務組件的好壞,尚未統一的標準。咱們在實踐中試着總結了幾條,供你們交流參考。

  • 組件可單獨編譯與運行,不須要依賴主工程。
  • 組件橫向之間沒有侵入性,組件修改後不會影響跟它位於同一層次的組件,PM不用擔憂改了A,壞了B的問題。
  • 組件在不一樣項目中具備較高的可移植性。能夠快速的移植到新的項目或者現有的別的項目中,而不用對別的項目進行較大的改動。

結語

組件化是一個漫長、繁瑣、複雜但有意義的過程,是一項團隊性的工做,建議你們在過程中增強團隊成員之間的溝通,遇到問題及時解決,及時調整,定好方向後就只管大膽地往前走。同時也歡迎你們與咱們溝通交流,但願咱們的分享可以在實踐中幫到你們!預祝你們新年快樂~

掘金年度徵文 | 2018 與個人技術之路 徵文活動正在進行中......

相關文章
相關標籤/搜索