我們知道,軟件開(kāi)發(fā)項(xiàng)目是一個(gè)綜合平衡的過(guò)程,要平衡時(shí)間、成本、范圍、質(zhì)量四個(gè)要素,在單個(gè)項(xiàng)目中,這四要素是非此即彼的:時(shí)間緊迫就要壓縮需求范圍,添加需求就要追加成本,確保質(zhì)量就不能過(guò)于壓縮工期,相互之間無(wú)法調(diào)和。
但如果跳出單個(gè)項(xiàng)目,在日常積累上面下功夫,我們卻有可能找到一種同時(shí)有利于項(xiàng)目四要素的途徑,就是建立和使用通用的開(kāi)發(fā)架構(gòu)。
大部分公司不會(huì)僅研發(fā)一個(gè)App,而是會(huì)研發(fā)一系列App,形成家族化、品牌化,或互相依賴,或入場(chǎng)試錯(cuò),這些App功能業(yè)務(wù)可能不盡相同,但一般都需要網(wǎng)絡(luò)模塊/日志模塊/圖像加載模塊,需要一些常見(jiàn)的簡(jiǎn)單函數(shù),為建立品牌形象,還需要統(tǒng)一的主題資源如色調(diào)/圖標(biāo)/提示語(yǔ)等。
這些低水平的重復(fù)開(kāi)發(fā),是與業(yè)務(wù)沒(méi)有直接關(guān)系卻必須支付的“死重”,是可以通過(guò)模塊復(fù)用來(lái)提升效率的,這也是我們做Android架構(gòu)分層的初衷,我們把開(kāi)發(fā)中常用的模塊抽象出來(lái),分組分層,形成結(jié)構(gòu)清晰,組裝靈活的通用組件庫(kù),支撐起了多個(gè)App的快速實(shí)現(xiàn)與迭代。
image
價(jià)值
在實(shí)際開(kāi)發(fā)過(guò)程中,我們發(fā)現(xiàn)通用組件庫(kù)對(duì)于開(kāi)發(fā)的效率和質(zhì)量,都有了顯著的提升:
節(jié)省時(shí)間,因?yàn)榻M件功能可以復(fù)用,能降低團(tuán)隊(duì)成員熟悉項(xiàng)目的成本,為新業(yè)務(wù)開(kāi)發(fā)提供基礎(chǔ),加快開(kāi)發(fā)迭代速度,有利于更快地發(fā)布版本。
降低成本,把穩(wěn)定的公共模塊抽象為通用組件庫(kù),提供給各個(gè)業(yè)務(wù)線協(xié)作使用,能在全公司范圍內(nèi)減少重復(fù)開(kāi)發(fā)和升級(jí)維護(hù)的工作量
提升質(zhì)量,頻繁使用的功能/業(yè)務(wù)模塊采用組件復(fù)用方式,更有利于暴露缺陷,一處修改,多處受益,提高產(chǎn)品質(zhì)量。
image
具體設(shè)計(jì)
對(duì)于App來(lái)說(shuō),選用組件應(yīng)該按需取用,僅選用自己需要的那些組件,這就需要把組件分離為多個(gè),形成一個(gè)結(jié)構(gòu)化的組件庫(kù),我們最終形成的組件庫(kù)大概是這樣的:
在上圖的結(jié)構(gòu)中,通用組件是與業(yè)務(wù)無(wú)關(guān)的基礎(chǔ)功能,共享組件是與業(yè)務(wù)有緊密聯(lián)系的,共享組件可能需要引用通用組件。
在具體實(shí)現(xiàn)中,我們處理過(guò)這樣幾個(gè)問(wèn)題:
引用形式:在引用形式上,我們有aar和module代碼兩種方式,其中aar適合函數(shù)已經(jīng)固定,不允許擴(kuò)展修改的情況;module適合類型已經(jīng)分開(kāi),但是函數(shù)并未固定,可以增加新函數(shù)的情況。
依賴倒置:在引用第三方庫(kù)時(shí),我們禁止直接引用,App可以直接引用第三方庫(kù),但是組件必須使用自己的接口,這樣在第三方庫(kù)升級(jí)或者更換時(shí),不會(huì)影響頂層的app。例如網(wǎng)絡(luò)層必須使用網(wǎng)絡(luò)組件自己定義的callback接口,實(shí)際上就是都要依賴于抽象,不能依賴具體。
接口隔離:組件庫(kù)大量使用接口為App服務(wù),這要求接口保持互相隔離,盡量把功能拆分到多個(gè)接口里,不能出現(xiàn)大而全的接口。
單一職責(zé):每個(gè)組件僅負(fù)責(zé)一類功能,互相之間可以有調(diào)用,但不能出現(xiàn)一個(gè)大而全的組件。
開(kāi)放封閉:組件中的函數(shù)是嚴(yán)禁修改的,可以增加新函數(shù),但嚴(yán)禁修改已有函數(shù),除非是為了消除缺陷。
異常拋出:底層組件有時(shí)候必須做異常捕獲,無(wú)論是Exception還是Error都需要拋出,也就是說(shuō)所有的Throwable都需要向上層拋出,避免應(yīng)用層莫名其妙的發(fā)現(xiàn)流程被打斷,無(wú)法查知底層組件出現(xiàn)的異常。
質(zhì)量控制:底層組件的場(chǎng)景比較抽象也比較固定,實(shí)際上容易做單元測(cè)試和自動(dòng)化測(cè)試,為組件開(kāi)發(fā)專門的自動(dòng)測(cè)試模塊,甚至出一個(gè)自動(dòng)測(cè)試demo,都是性價(jià)比很高的投入。
代碼追溯:對(duì)于module形式共享的組件,實(shí)際上允許開(kāi)發(fā)團(tuán)隊(duì)進(jìn)行擴(kuò)展和修改,但是所有的變更都隱藏著缺陷,所以在組件庫(kù)的代碼提交中,必須進(jìn)行代碼審查,并注釋代碼修改的時(shí)間、事由、操作人等,以便在出現(xiàn)缺陷時(shí)進(jìn)行追溯 。
jar包沖突:長(zhǎng)期維護(hù)下來(lái),必然可能引用多個(gè)版本的第三方庫(kù),這就會(huì)產(chǎn)生jar包沖突的問(wèn)題,所以有必要在底層建立一個(gè)整合第三方庫(kù)的module,各App共同引用這個(gè)庫(kù)module。
版本分支:有些情況下,某些第三方庫(kù)發(fā)生了大版本的迭代更新,更新前后的功能變化極大,導(dǎo)致app無(wú)法完美兼容,這就需要建立版本分支,使用特定版本的組件庫(kù),維持app的研發(fā)需求,直至app重構(gòu),或app消亡。
路由解耦:有些業(yè)務(wù)組件是有Activity的,這些組件之間跳轉(zhuǎn)時(shí),為了解耦合,應(yīng)該避免通過(guò)包名和類名去跳轉(zhuǎn),可以參考Android的Intent思想,允許通過(guò)action和category一起過(guò)濾,找到跳轉(zhuǎn)目標(biāo),實(shí)際開(kāi)發(fā)中可以做一個(gè)Router,例如阿里開(kāi)源的ARouter
最終,App選用組件庫(kù)的結(jié)構(gòu),大概是這樣的: