項(xiàng)目中第三方庫(kù)并不是必須的
前言
我在Lyft的八年間,很多產(chǎn)品經(jīng)理以及工程師經(jīng)常想往我們 app 里添加第三方庫(kù)。有時(shí)候集成一個(gè)特定的庫(kù)(比如 PayPal)是必須的,有時(shí)候是避免去開發(fā)一些非常復(fù)雜的功能,有時(shí)候僅僅只是避免重復(fù)造輪子。
雖然這些都是合理的考量,但使用第三方庫(kù)的風(fēng)險(xiǎn)和相關(guān)成本往往被忽視或誤解。在某些情況下,風(fēng)險(xiǎn)是值得的,但是在決定冒險(xiǎn)之前,首先要能夠明確的定義風(fēng)險(xiǎn)。為了使風(fēng)險(xiǎn)評(píng)估更加的透明和一致,我們制定了一個(gè)流程來(lái)衡量我們將其集成到app有多大的風(fēng)險(xiǎn)。
風(fēng)險(xiǎn)
大多數(shù)大型組織,包括我們,都有某種形式的代碼審查,作為開發(fā)實(shí)踐的一部分。對(duì)這些團(tuán)隊(duì)來(lái)說(shuō),添加一個(gè)第三方庫(kù)就相當(dāng)于添加了一堆由不屬于團(tuán)隊(duì)成員開發(fā),未經(jīng)審查的代碼。這破壞了團(tuán)隊(duì)一直堅(jiān)持的代碼審查原則,交付了質(zhì)量未知的代碼。這給app的運(yùn)行方式以及長(zhǎng)期開發(fā)帶來(lái)了風(fēng)險(xiǎn),對(duì)于大型團(tuán)隊(duì)而言,更是對(duì)整體業(yè)務(wù)帶來(lái)了風(fēng)險(xiǎn)。
運(yùn)行時(shí)風(fēng)險(xiǎn)
庫(kù)代碼通常來(lái)說(shuō),對(duì)于系統(tǒng)資源,和app擁有相同級(jí)別的訪問(wèn)權(quán)限,但它們不一定應(yīng)用團(tuán)隊(duì)為管理這些資源而制定的最佳實(shí)踐。這意味著它們可以在沒(méi)有限制的情況下訪問(wèn)磁盤,網(wǎng)絡(luò),內(nèi)存,CPU等等,因此,它們可以(過(guò)度)將文件寫入磁盤,使用未優(yōu)化的代碼占用內(nèi)存或CPU,導(dǎo)致死鎖或主線程延遲,下載(和上傳?。┐罅繑?shù)據(jù)等等。更糟糕的是他們會(huì)導(dǎo)致崩潰,甚至崩潰循環(huán)。兩次。
其中許多情況直到 app 已經(jīng)上架才被發(fā)現(xiàn),在這種情況下,修復(fù)它需要?jiǎng)?chuàng)建一個(gè)新版本,并通過(guò)審核,這通常需要大量時(shí)間和成本。這種風(fēng)險(xiǎn)可以通過(guò)一個(gè)變量控制是否調(diào)用來(lái)進(jìn)行一定程度的控制,但是這種方法也并非萬(wàn)無(wú)一失(看下文)。
開發(fā)風(fēng)險(xiǎn)
引用一個(gè)同事的話:“每一行代碼都是一種負(fù)擔(dān)”,對(duì)不是你自己寫的代碼而言,這句話更甚。庫(kù)在適配新技術(shù)或API時(shí)可能很慢,這阻礙了代碼開發(fā),或者太快,導(dǎo)致開發(fā)的版本過(guò)高。
庫(kù)在采用新技術(shù)或API時(shí)可能很慢,阻礙了代碼庫(kù),或者太快,導(dǎo)致部署目標(biāo)太高。每當(dāng) Apple 和 Google 每年發(fā)布一個(gè)新 OS 版本時(shí),他們通常要求開發(fā)人員根據(jù)SDK的變化更新代碼,庫(kù)開發(fā)人員也必須這樣做。這需要協(xié)調(diào)一致的努力、優(yōu)先事項(xiàng)的一致性以及及時(shí)完成工作的能力。
隨著移動(dòng)平臺(tái)的不斷變化,以及團(tuán)隊(duì)(成員)也不是一成不變,這將會(huì)成為一個(gè)持續(xù)不斷的風(fēng)險(xiǎn)。當(dāng)被集成的庫(kù)不存在了,而庫(kù)又需要更新時(shí),會(huì)花很多時(shí)間來(lái)決定誰(shuí)來(lái)做。事實(shí)證明一旦一個(gè)庫(kù)存在,就很少也很難被移除,因此我們將其視為長(zhǎng)期維護(hù)成本。
商業(yè)風(fēng)險(xiǎn)
如同我上面所說(shuō),現(xiàn)代的操作系統(tǒng)并沒(méi)有對(duì) app 代碼和庫(kù)代碼進(jìn)行區(qū)分,因此除了系統(tǒng)資源之外,它們還可以訪問(wèn)用戶信息。作為 app 的開發(fā)者,我們負(fù)責(zé)恰當(dāng)?shù)氖褂眠@部分信息,也需要為任何第三方庫(kù)負(fù)責(zé)。
如果用戶給了 Lyft app 地理位置授權(quán),任何第三方庫(kù)也將自動(dòng)得獲得授權(quán)。他們可以將那些(地理位置)數(shù)據(jù)上傳到自己服務(wù)器,競(jìng)對(duì)服務(wù)器,或者誰(shuí)知道還有什么地方。當(dāng)一個(gè)庫(kù)需要我們沒(méi)有的權(quán)限時(shí),那問(wèn)題就更大了。
同樣,一個(gè)系統(tǒng)的安全取決于其最薄弱的環(huán)節(jié),但如果其中包含未經(jīng)審核的代碼,那么你就不知道它到底有多安全。你精心設(shè)計(jì)的安全編碼實(shí)踐可能會(huì)被一個(gè)行為不當(dāng)?shù)膸?kù)所破壞。蘋果和谷歌實(shí)施的任何政策都是如此,例如“你不得對(duì)用戶追蹤”。
減少風(fēng)險(xiǎn)
當(dāng)對(duì)一個(gè)庫(kù)(是否)進(jìn)行使用評(píng)估時(shí),我們首先要問(wèn)幾個(gè)問(wèn)題,以了解對(duì)庫(kù)的需求。
我們內(nèi)部能做么?
有時(shí)候我們只需要簡(jiǎn)單的粘貼復(fù)制真正需要的部分。在更復(fù)雜的場(chǎng)景中,庫(kù)與自定義后端通信,我們對(duì)該API進(jìn)行了逆向,并自己構(gòu)建了一個(gè)迷你SDK(同樣,只構(gòu)建了我們需要的部分)。在90%的情況下,這是首選,但在與非常特定的供應(yīng)商或需求集成時(shí)并不總是可行。
有多少用戶從該庫(kù)中受益?
在一種情況下,我們正在考慮添加一個(gè)風(fēng)險(xiǎn)很大的庫(kù)(根據(jù)下面的標(biāo)準(zhǔn)),旨在為一小部分用戶提供服務(wù),同時(shí)將我們的所有用戶都暴露在該庫(kù)中。對(duì)于我們認(rèn)為會(huì)從中受益的一小部分客戶,我們冒了為我們所有用戶帶來(lái)問(wèn)題的風(fēng)險(xiǎn)。
這個(gè)庫(kù)有什么傳遞依賴?
我們還需要評(píng)估庫(kù)的所有依賴項(xiàng)的以下標(biāo)準(zhǔn)。
退出標(biāo)準(zhǔn)是什么?
如果集成成功,是否有辦法將其轉(zhuǎn)移到內(nèi)部?如果不成功,是否有辦法刪除?
評(píng)價(jià)標(biāo)準(zhǔn)
如果此時(shí)團(tuán)隊(duì)仍然希望集成庫(kù),我們要求他們根據(jù)一組標(biāo)準(zhǔn)對(duì)庫(kù)進(jìn)行“評(píng)分”。下面的列表并不全面,但應(yīng)該能很好地說(shuō)明我們希望看到的。
阻斷標(biāo)準(zhǔn)
這些標(biāo)準(zhǔn)將阻止我們從技術(shù)上或者公司政策上集成此庫(kù),在進(jìn)行下一步之前,我們必須解決:
過(guò)高的 deployment target/target SDKs。 我們支持過(guò)去4年主流的操作系統(tǒng)(版本),所以第三方庫(kù)至少也需要支持一樣多。
許可證不正確/缺失。 我們將許可文件與應(yīng)用捆綁在一起,以確保我們可以合法使用代碼并將其歸屬于許可持有人。
沒(méi)有沖突的傳遞依賴關(guān)系。 一個(gè)庫(kù)不能有一個(gè)我們已經(jīng)包含但版本不同的傳遞依賴項(xiàng)。
不顯示它自己的 UI 。 我們非常小心地使我們的產(chǎn)品看起來(lái)盡可能統(tǒng)一,定制用戶界面對(duì)此不利。
它不使用私有 API 。 我們不愿意冒 app 因使用私有 API 而被拒絕的風(fēng)險(xiǎn)。
主要關(guān)注點(diǎn)
閉源。 訪問(wèn)源代碼意味著我們可以選擇我們想要包含的庫(kù)的哪些部分,以及如何將該源代碼與應(yīng)用程序的其余部分捆綁在一起。對(duì)于我們來(lái)說(shuō),一個(gè)封閉源代碼的二進(jìn)制發(fā)行版更難集成。
編譯時(shí)有警告。 我們啟用了“警告視為錯(cuò)誤”,具有編譯警告的庫(kù)是庫(kù)整體質(zhì)量(下降)的良好指示。
糟糕的文檔。 我們希望有高質(zhì)量的內(nèi)聯(lián)文檔,外部”如何使用“文檔,以及有意義的更新日志。
二進(jìn)制體積。 這個(gè)庫(kù)有多大?一些庫(kù)提供了很多功能,而我們只需要其中的一小部分。尤其是在沒(méi)有訪問(wèn)源碼權(quán)限的情況下,這通常是一個(gè)全有或全無(wú)的情況。
外部的網(wǎng)絡(luò)流量。 與我們無(wú)法控制的上游服務(wù)器/端點(diǎn)通信的庫(kù)可能會(huì)在服務(wù)器關(guān)閉、錯(cuò)誤數(shù)據(jù)被發(fā)回等時(shí)關(guān)閉整個(gè)應(yīng)用程序。這也與我上面提到的隱私問(wèn)題相同。
技術(shù)支持。 當(dāng)事情不能正常工作時(shí),我們需要能夠報(bào)告/上報(bào)問(wèn)題,并在合理的時(shí)間內(nèi)解決問(wèn)題。開源項(xiàng)目通常由志愿者維護(hù),也很難有一個(gè)時(shí)間線,但至少我們可以自己進(jìn)行修改。這在閉源項(xiàng)目是不可能的。
無(wú)法禁用。 雖然大多數(shù)庫(kù)特別要求我們初始化它,但有些庫(kù)在實(shí)例化時(shí)更“主動(dòng)”,并且在我們不調(diào)用它的情況下可以自己執(zhí)行工作。這意味著當(dāng)庫(kù)導(dǎo)致問(wèn)題時(shí),我們無(wú)法通過(guò)功能變量或其他機(jī)制將其關(guān)閉。
我們?yōu)樗羞@些(和其他一些)標(biāo)準(zhǔn)分配了點(diǎn)數(shù),并要求工程師為他們想要集成的庫(kù)匯總這些點(diǎn)數(shù)。雖然默認(rèn)情況下,低分?jǐn)?shù)并不難被拒絕,但我們通常會(huì)要求更多的理由來(lái)繼續(xù)前進(jìn)。
最后
雖然這個(gè)過(guò)程看起來(lái)非常嚴(yán)格,在許多情況下,潛在風(fēng)險(xiǎn)是假設(shè)的,但我們有我在這篇博文中描述的每個(gè)場(chǎng)景的實(shí)際例子。將評(píng)估記錄下來(lái)并公開,也有助于將相對(duì)風(fēng)險(xiǎn)傳達(dá)給不熟悉移動(dòng)平臺(tái)工作方式的人,并證明我們沒(méi)有隨意評(píng)估風(fēng)險(xiǎn)。
此外,我不想聲稱每一個(gè)第三方庫(kù)本質(zhì)上都是壞的。事實(shí)上,我們?cè)贚yft使用了很多:RxSwift和RxJava、Bugsnag的SDK、Google Maps、Tensorflow,以及一些較小的用于非常特定的用例。但所有這些要么都經(jīng)過(guò)了充分審查,要么我們已經(jīng)決定風(fēng)險(xiǎn)值得收益,同時(shí)對(duì)這些風(fēng)險(xiǎn)和收益的真正含義有了清晰的認(rèn)識(shí)。
最后,作為一個(gè)專業(yè)開發(fā)人員提示:始終在庫(kù)的API之上創(chuàng)建自己的抽象,不要直接調(diào)用它們的API。這使得將來(lái)替換(或刪除)底層庫(kù)更加容易,再次減輕了與長(zhǎng)期開發(fā)相關(guān)的一些風(fēng)險(xiǎn)。