組件化是一個不可避免的階段,個人認為也是一個越早越好的階段。特別在一個公司內(nèi)的產(chǎn)品越來越多的時候,顯得尤為重要。
什么是組件化
組件化的字面意思,是代碼上可復(fù)用的結(jié)果,而實際上組件化遠不止這個層面。
在沒有組件化的時候,每個項目都是各做各的事情,包括一個組內(nèi)也是存在重復(fù)造輪子的行為,等到需要抽離獨立模塊的時候,又發(fā)現(xiàn)耦合過大,花費大量人力來解耦或者重構(gòu),甚至不了了之。
新項目的快速成型
立項 ---> 確定需求 ---> 搭建主工程框架 ---> 引入需要的組件 ---> 補充沒有的組件 ---> 成品
在一定的積累下,一個公司內(nèi)部的新產(chǎn)品開發(fā),都可以快速的搭建起一個擁有公司基礎(chǔ)業(yè)務(wù)組件的框架,專心做差異的業(yè)務(wù)開發(fā),而無須重新花費力氣重新開發(fā)這些基礎(chǔ)功能,例如一些性能監(jiān)控,數(shù)據(jù)上報,測試框架等。
研發(fā)思維于流程的質(zhì)變
組件化并不局限于基礎(chǔ)底層的東西,業(yè)務(wù)邏輯,界面封裝都可以成為一個組件,例如通用下發(fā)配置,相機相冊界面等,都可以成為大組件化的一個積累過程。
在這樣的環(huán)境下,研發(fā)的開發(fā)流程會變成下面這樣
獲得需求 ---> 前期分析和設(shè)計 ---> Coding ---> 組件化(如果可以) ---> 完成開發(fā)
區(qū)別在于,在組件化的大環(huán)境下,大家在開發(fā)前期,會思考這個需求是否可以成為一個通用方案,而進行一些前期的分析和設(shè)計,可以的話,就會從中找到可以組件化的部分,成為一個獨立模塊。即使不能組件化,也會對于后續(xù)這個模塊的低耦合方面有所貢獻,引導(dǎo)大家面向接口編程,保持下層穩(wěn)定性。
潛在的激勵
每個研發(fā)都希望自己的代碼得到認可,組件化可以潛在的推動這個事情。從組內(nèi)組件化,到跨項目通用,到公司內(nèi)公用,最后發(fā)展成對外開源,是一個不斷鼓勵代碼優(yōu)化和維護的過程。
探索之路
早期的開發(fā)可能都會經(jīng)歷過這么幾個階段,現(xiàn)在或許直接就到了最后的形態(tài),再往后就在于是否堅持了。
以下列出了幾種我經(jīng)歷過的工程形態(tài),有些或許現(xiàn)在還是這樣,我們可以順著理一下,這里都是基于iOS的開發(fā)來討論。
一個工程走天下
在早期,或者現(xiàn)在還有些項目,一個app只有一個主程序,所有代碼按照文件夾來劃分,實際上都在一個target上,很多開發(fā)會覺得很方便啊,這樣有什么不好呢?
- 容易造成import泛濫
- import沒有顯示來源,不利于代碼理解和解耦
- 沒有一個很好的尋找特定代碼的方向,容易重復(fù)造輪子
- 文件夾劃分太弱,不利于大家規(guī)劃同類的東西在一起,甚至一些通用的基礎(chǔ)方法直接寫在業(yè)務(wù)代碼里
- 比如系統(tǒng)的Foundation,如果我找一個字符串操作相關(guān),我一下子就知道該去這里找了
- 后期要解耦或者抽離的時候,工作量很大,如果還有交叉循環(huán)依賴,就更痛苦了
- 代碼沒有得到隔離
- 太容易觸碰到的東西,很容易誘發(fā)不能用就改實現(xiàn)的行為
- 或者完全基于實現(xiàn)來使用接口,沒有達到一個面向接口編程的目的
- owner也沒有動力完善接口,基于實現(xiàn)告訴使用者怎么用
- 跨項目復(fù)雜度高
- 代碼復(fù)用全靠copy
- 修改bug容易另外一個項目忘記修改
- 如果實現(xiàn)得太業(yè)務(wù)化,另外一邊也用不起來
當然也不能一棍子打死所有案例,有些app就是簡單,沒必要那么復(fù)雜化,但是怎么也會有一些自定義的基礎(chǔ)類封裝吧,統(tǒng)一管理出來是個好習(xí)慣,說不定那天會感激自己的多此一舉的
至于公司級別的app,基本上就不會簡單到那個地步了,至少我個人是這么覺得的。
多工程模式
多工程模式下,一般有點規(guī)模的一個功能,都會獨立成一個工程,放在主工程下,成為一個子工程,主工程依賴這個target即可。
多工程和多文件夾的最大區(qū)別在于
- 工程可以有不一樣的配置,文件夾只是很弱的視覺劃分
- 工程有明確的一個獨立的姿態(tài)
- 工程一般會有一個Dependency的文件夾,把依賴的文件,庫,子工程放這里,一目了然
- 哪天需要復(fù)用,我很快了解這些依賴有什么,解決這些依賴就可以了,文件夾的話,你需要看完每個文件的
.h和.m,才能匯總起來一共依賴了些什么 - 方便整個功能移植到其他app,或者在demo上測試開發(fā),不影響主項目的流程
這里的好處是沒有異議的,但是怎么很好地解決依賴問題是一個探索的過程
0x1 拖拽文件
從早期我們遇到一個子工程,需要一來到另外一個子工程的文件的時候,第一想到的辦法就是把那些文件的.h拖到這個工程來,然后發(fā)現(xiàn)這個.h又依賴了另外的.h,直到把所有頭文件都拖進來了,總算完事了。
但是突然有一個文件上的改動,這個行為可能要重新做一次,這就很恐怖了。
0x2 子工程化依賴
既然我們已經(jīng)用工程化的行為來模塊化了每個業(yè)務(wù)或者說每個功能集,為什么不還局限于對某個文件的依賴呢?
你依賴一個業(yè)務(wù)里面的文件,很大概率你就會用到這個業(yè)務(wù)里的其他文件,既然這樣,我們就把文件級別的依賴升華到工程級別的依賴。
也就意味著我們不再一個文件一個文件的拖,而是整個子工程拖到自己的工程下面,那么這個工程就可以訪問整個子工程的公開文件了,何樂而不為呢?
0x3 統(tǒng)一工程化依賴
但是我們發(fā)現(xiàn),依然沒有完全解決這個問題,想象一下,如果某個工程被其他很多工程都依賴了,而這個工程位置變了,一個基礎(chǔ)模塊工程,一下子需要放到很多很多工程下,這個操作也是很難受的。
我們想了一下,創(chuàng)建了一個Bridge工程,所有工程都依賴Bridge工程,同時所有工程都是Bridge工程下的子工程。
乍看之下好像有點死循環(huán),其實不然。
-
Bridge工程的編譯并沒有依賴它的子工程的運行,而是依賴子工程下的腳本,但是子工程依賴Bridge工程的編譯 -
Bridge工程的編譯會引發(fā)每個子工程腳本,生成自己的頭文件路徑,從而給到使用者 - 主程序同樣不依賴
Bridge工程,還是按順序依賴其他真是子工程的編譯
這種情況下,為了方便大家,還創(chuàng)建了一個工程模板,統(tǒng)一標準創(chuàng)建,且附帶好所有腳本,唯一需要做的,就是把自己拖進Bridge工程的依賴當中。
0x4 腳本時代
說到底,Bridge工程 開始還是需要人為拖動,且不利于新人理解
我們回歸到最原始最本質(zhì)的問題上:可以使用目標.h文件
清晰了這么一點就好辦了
- 每個工程只需要搭配一個配置文件,聲稱我這里哪些文件是Public的,這樣就可以了
- 然后我們寫一個腳本,遍歷工程文件夾下所有配置文件,在對應(yīng)的目錄生成這些Public Header的soft-link,這樣每個子工程都有自己的一個Public Headers的目錄
- 使用者引用對應(yīng)的路徑即可
(可能很多人覺得這只是一個framework的事情,但是那時候還沒有framework,而且目前一些老項目說不定還在支持iOS7)
腳本能帶來的好處是很大的,除了方便了很多以外,甚至可以做到任何交叉循環(huán)依賴,在每個子工程上可以說用的風(fēng)生水起了。
但是埋下的問題也是很可怕的
- 首先是這套方案是內(nèi)部使用的,并不能推廣出去,連跨組推廣都有一定的復(fù)雜度,更別說公司外了
- 然后這套方便的方案,在設(shè)計模式層面上,可以說破壞的很厲害了,會削弱研發(fā)的設(shè)計思維
- 使用過程沒有規(guī)范化,新人有一定的學(xué)習(xí)和理解成本
Workspace模式
0x5 Pod的到來
Pod的規(guī)范化可以說帶來了一個最終的項目工程形態(tài)(當然也有些公司有自己更好的一套方案),主要得益于:
- 業(yè)內(nèi)的一個通用標準和規(guī)范,降低相互之間的一個學(xué)習(xí)成本
- 更深一層的代碼隔離,更好的代碼引入方式,更豐富的配置
- 對私對公的repo機制,插件可自定制化,更少的人為配置依賴操作
- 不同分支倉庫的組件差異化
對于Pod的模式和使用上,這里就沒必要多說了,可以到Cocoapods的官網(wǎng)了解詳細的內(nèi)容,也可以Google一些插件定制化的東西來滿足特定的需求。
這里先回答一個疑問,Pod畢竟也不是很晚期的東西了,為什么早期或者中期不直接開始引入這個模式呢?
一個客觀原因是,在當時Pod剛起步,或者說還沒有到現(xiàn)在這么普及,我們還在觀望和不熟悉的階段,的確是有點躊躇,通俗點說,對于陌生的東西,就是有點慫。
另外一個是歷史原因,我們雖然看到前面幾個階段有很多不規(guī)范不成熟的地方,但是它們在當時的確是一個相對較佳的方案,我們來分析一下為什么:
- 這些舊方式都沒有任何難度,想到一個優(yōu)化點就可以馬上動手,對原來的代碼框架修改很少,可以說非常輕量化
- 0x4模式雖然很大程度上破壞了設(shè)計模式層面,但是不得不說在中國互聯(lián)網(wǎng)公司快速開發(fā)迭代的節(jié)奏下,還是獲得很多人的青睞,畢竟項目的發(fā)版壓力還是有的
- 歷史代碼的解耦難度大,一下子需要抽離出來,不是一件容易的事情,另外大家對歷史 相對穩(wěn)定 的代碼,也是睜一只眼閉一只眼的態(tài)度
阻力是什么
從前面的分析看來,不難發(fā)現(xiàn),組件化的阻力從來不是技術(shù)上的,而是在于流程上的。
從技術(shù)層面上分析,組件化無非就是
-
選定組件化的方式- Pod的方式對我來說已經(jīng)非常合適了,不行就加點定制化的東西進去
- 如果牛逼一點的公司可以有自己更優(yōu)的方案,但是畢竟維護一整套東西也是個成本
-
代碼解耦抽離- 從快速抽離的角度來看,解耦的做法,沒有什么是一個中間層解決不了的,如果有,就兩個
- 復(fù)雜的模塊頂多多費點時間,缺乏人力的情況下,甚至可以大合集先包含進去,盡早先收攏再優(yōu)化
-
后期維護- 只是一個繼續(xù)開發(fā)的過程而已
阻力從來都出現(xiàn)在流程上
組件化的推進,畢竟不是一個組的事情,也不能局限在一個部門上。在所有方案都確定好的時候,你發(fā)現(xiàn)需要跨團隊跨部門跨子公司來溝通的時候,才是真正的阻力。因為每個部門每個組的情況都不一樣,他們有著各自的項目壓力和技術(shù)方向,更有著不一樣的想法。
如何推進
推進的過程,我個人覺得是個藝術(shù)活,論外交手腕的重要性(會心一笑)
因為技術(shù)方案都確定好了之后,剩下的技術(shù)手段都是苦力活,真正花時間的可能都是交際手段
- 推銷方案,并得到認同,保持步調(diào)一致
-
確定前期目標,收攏的目標項目有哪些,整理出來的組件模塊有哪些,從而確定開刀的
主客項目是誰-
主客是誰這個很關(guān)鍵,相當于確定公共模塊的標準是誰,用誰的代碼,以后誰來維護
-
-
資源分配,也可以說是工作模式,最大的阻力環(huán)節(jié)
- 各個組來各自安排,這樣會由于各自的項目壓力和人力,把周期拖得很長
- 統(tǒng)一出人來幫忙各個組抽離組件,這樣涉及到權(quán)限的問題,流程上就有點周折
- 另外還需要有人負責(zé)打通平臺對接,使得涉及的平臺支持新的方案
-
效果檢驗,這個是最重要的環(huán)節(jié),不讓其他人看到效果,這個事情只會是一次徒勞的苦力活
- 最容易看到的效果,就是拿新項目來試水,對比出成品的時間
- 長期來看的話,就是內(nèi)部開源項目的數(shù)量,組件化的數(shù)量,甚至對外開源的項目情況
-
持續(xù)維護,需求是源源不斷的,肯定需要有人持續(xù)跟進這個開發(fā)流程上的問題
- 需要持續(xù)優(yōu)化過程中遇到的問題,需要高保證不出錯
- 提升使用組件化的帶來的副作用而影響的效率
這個過程如果是從上往下推動的話,顯然比從下往上要容易得多。
最后
組件化顯然是一個不可避免的趨勢,如前面提到的,我個人認為是越早開始越好,對于開發(fā)人員的思維定性來說,一旦長期沒有這個概念,亂糟糟的代碼是不可避免的,也是會互相傳染而泛濫。
雖然很多產(chǎn)品現(xiàn)在都是不在乎實現(xiàn),但求無錯不崩潰的態(tài)度,顯示也不是一個可持續(xù)發(fā)展的做法,一定規(guī)模的公司更是要避免這種短期成品的行為。
也有一部分公司覺得需要的人力很大,在業(yè)務(wù)重壓之下沒有任何資源來做這件事情。我個人認為也是一個比較極端武斷的想法,組件化并不要求一次性到達一個完美的階段,它和業(yè)務(wù)一樣是一個重復(fù)迭代優(yōu)化的過程,在即使最糟糕的情況下,我也可以把所有明顯的獨立模塊大集合先抽離出來,不要求代碼有任何改動,只是單純的抽離,達到一個可用的階段即可,這樣就可以先讓全公司的項目,先用上同一份代碼,無論是資源的釋放,還是邏輯上的統(tǒng)一,都是一個非常好的開端,總之代碼的收攏越早越好。
一些公司也有自己的SDK組,這些其實是組件化的前身,如果能進一步去演進和統(tǒng)一操作,相信會得到更多的好處。
以上僅代表本人個人想法,歡迎吐槽和交流