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