新聞中心
在軟件開發(fā)中,依賴項(xiàng)是程序員想要調(diào)用的附加代碼。添加依賴項(xiàng)可以避免重復(fù)工作,例如設(shè)計(jì)、測試、調(diào)試和維護(hù)特定的代碼單元,這個(gè)代碼單元被稱為包,或者庫,或者模塊等,本文會(huì)混用。采用軟件依賴項(xiàng)很常見,咱們都經(jīng)歷過手動(dòng)安裝所需庫的步驟,比如 C 的 PCRE 或 zlib; C++的 Boost 或 Qt; 或 Java 的 JUnit等。這些軟件庫包含了高質(zhì)量且經(jīng)過調(diào)試的代碼,需要大量的專業(yè)知識(shí)來開發(fā)。對(duì)于一個(gè)需要這些軟件包提供的功能的程序來說,手動(dòng)下載、安裝和更新軟件包的工作要比從頭開始開發(fā)這些功能要容易得多。

成都創(chuàng)新互聯(lián)是一家專業(yè)提供政和企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)、H5建站、小程序制作等業(yè)務(wù)。10年已為政和眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計(jì)公司優(yōu)惠進(jìn)行中。
依賴管理器,也稱為包管理器,可以自動(dòng)下載和安裝依賴包。由于依賴管理器使單個(gè)軟件包更容易下載和安裝,成本較低, 使得發(fā)布和重用較小的軟件包更經(jīng)濟(jì)。例如,Node.js 的依賴管理器 NPM 提供了對(duì)超過幾十萬個(gè)包的訪問?,F(xiàn)在基本上每種編程語言都有依賴管理器: Maven (Java)、 Composer(PHP)和pip (Python)等都超過了10萬個(gè)包。
這種細(xì)粒度的、廣泛的軟件復(fù)用的到來是這些年來軟件開發(fā)中最重要的轉(zhuǎn)變之一。然而,如果我們不更加小心,就會(huì)導(dǎo)致嚴(yán)重的問題。
1. 依賴的演變
包或者庫都是從 Internet 下載的代碼,將一個(gè)包作為依賴項(xiàng)添加自己的程序中,該程序暴露依賴項(xiàng)中的所有失敗和缺陷,因?yàn)樗耆蕾囉谶@些下載的代碼。這種方式聽起來非常不安全。為什么人們這么做?因?yàn)樗芎唵?,看起來很有效,是引用?nèi)部依賴的自然延續(xù)。
過去,大多數(shù)開發(fā)人員都信任自己所依賴的軟件,比如操作系統(tǒng)和編譯器。這些軟件是從已知的來源購買的,雖然存在著漏洞的可能性,但至少開發(fā)者知道他們?cè)诤驼l打交道,通常有商業(yè)或法律資源可用。
在互聯(lián)網(wǎng)上免費(fèi)分發(fā)的開源軟件已經(jīng)取代了許多早期購買的軟件。一些項(xiàng)目建立起了眾所周知的聲譽(yù),例如早期軟件包 libjpeg (1991),HP STL (1994)和 zlib (1995)等,聲譽(yù)往往會(huì)成為了人們決定使用哪些依賴的重要因素,對(duì)信任軟件來源的商業(yè)和法律支持被聲譽(yù)支持所取代,這可能就是共識(shí)的力量。
依賴管理器進(jìn)一步縮小了開源代碼重用模型的規(guī)?!,F(xiàn)在,開發(fā)人員可以在由數(shù)十行代碼組成的單個(gè)函數(shù)的粒度上共享代碼,這是一項(xiàng)重大的技術(shù)成就。無數(shù)的軟件包是可用的,編寫代碼可能涉及大量的軟件包,但是用于信任代碼的商業(yè)、法律和聲譽(yù)支持機(jī)制并沒有繼續(xù)下去。開發(fā)人員信任更多的代碼,而不需要太多的理由。
然而,采用不良依賴的成本可以看作是每個(gè)不良結(jié)果的成本乘以其發(fā)生的可能性之和。使用依賴項(xiàng)的場景決定了壞結(jié)果的成本。如果只是個(gè)人愛好,其中大多數(shù)壞結(jié)果的成本幾乎為零,因?yàn)橹皇窃谙硎軜啡?,風(fēng)險(xiǎn)概率幾乎為零。但是,如果是一個(gè)維護(hù)多年的生產(chǎn)軟件,依賴關(guān)系中的 bug 的成本可能非常高: 服務(wù)器可能宕機(jī),敏感數(shù)據(jù)可能泄露,客戶可能受到傷害,甚至公司可能倒閉。高失敗成本使得評(píng)估和降低嚴(yán)重風(fēng)險(xiǎn)變得更加重要。
不管預(yù)期的成本是多少,都需要一些估計(jì)和減少添加軟件依賴性風(fēng)險(xiǎn)的方法??赡苄枰玫墓ぞ邅韼椭拖褚蕾嚬芾砥饕恢标P(guān)注于降低下載和安裝的成本那樣。
2. 依賴的檢查
在使用代碼依賴時(shí),基本的檢查可以讓我們了解遇到問題的可能性有多大。如果檢查中發(fā)現(xiàn)了可能出現(xiàn)的小問題,可以采取措施準(zhǔn)備或者避免它們。如果檢查中發(fā)現(xiàn)了大問題,最好不要使用這個(gè)包, 也許能夠找到一個(gè)更合適的,也許需要自己開發(fā)一個(gè)。開源軟件包是由其作者發(fā)布的,希望它們會(huì)有用,但是較少有可用性或支持的保證。系統(tǒng)掛了,不得不調(diào)試這些包,整個(gè)項(xiàng)目的質(zhì)量和性能風(fēng)險(xiǎn)都在我們自己身上。
因此,我們需要在依賴檢查時(shí)考慮一些因素。
2.1 設(shè)計(jì)
文件清楚嗎?API有清晰的設(shè)計(jì)嗎?如果作者能夠在文檔中很好地解釋依賴包的 API 及其設(shè)計(jì),那么他們?cè)谠创a中實(shí)現(xiàn)正確的可能性就會(huì)增加。使用清晰的、設(shè)計(jì)良好的 API 編寫代碼也更容易、更快,并且更少出錯(cuò)。作者是否記錄了他們對(duì)客戶端代碼的期望,以使升級(jí)兼容呢?(例如 C++ 23的兼容性文檔。)
2.2 代碼質(zhì)量
代碼寫得好嗎?讀一些代碼吧。作者看起來是否小心謹(jǐn)慎,始終如一?看到的代碼起我們想要調(diào)試的代碼嗎?需要有檢查代碼質(zhì)量的系統(tǒng)方法。例如,簡單地編譯一個(gè)啟用了重要編譯器警告的 c 或 c + + 程序(例如-Wall) ,就可以讓開發(fā)人員了解在避各種未定義行為方面的嚴(yán)重程度,看看有多少不安全的代碼。忽略關(guān)于死記硬背的建議,轉(zhuǎn)而關(guān)注語義問題。
對(duì)不熟悉的開發(fā)實(shí)踐要保持開放的心態(tài)。例如,SQLite 庫提供了一個(gè)單獨(dú)的200,000行 c 源文件和一個(gè)單獨(dú)的11,000行稱為 amalgamation 的頭文件。這些文件的大小會(huì)引起最初的警覺,但是深入進(jìn)去會(huì)發(fā)現(xiàn)實(shí)際的開發(fā)源代碼包含了一個(gè)100多個(gè) c 源文件、測試和支持腳本的文件樹。事實(shí)證明,單文件分發(fā)是從原始數(shù)據(jù)源自動(dòng)構(gòu)建的,對(duì)于最終用戶,尤其是那些沒有依賴項(xiàng)管理器的用戶來說更加容易。另外,編譯后的代碼也運(yùn)行得更快,因?yàn)榫幾g器可以看到更多的優(yōu)化機(jī)會(huì)。
2.3 測試
代碼有測試嗎?能運(yùn)行它們嗎?測試確定了代碼的基本功能是正確的,并且表明開發(fā)人員對(duì)于保持代碼的正確性是認(rèn)真的。例如,SQLite 開發(fā)樹有一個(gè)非常全面的測試套件,超過了30,000個(gè)單獨(dú)的測試用例,以及解釋測試策略的文檔。未來方案的修改可能會(huì)引入回歸測試,而這些回歸測試很容易被發(fā)現(xiàn)。
假設(shè)測試運(yùn)行通過,還可以運(yùn)行時(shí)檢測(如代碼覆蓋率分析、競爭檢測、內(nèi)存分配檢查和內(nèi)存泄漏檢測)來收集更多信息。
2.4 調(diào)試
找到包里的問題列表,里面有開放的 bug 報(bào)告嗎?使用多久了?是否有許多錯(cuò)誤尚未修復(fù)?最近有什么錯(cuò)誤被修復(fù)了嗎?如果看到很多關(guān)于 bug 的公開問題,而且已經(jīng)公開了很長一段時(shí)間,這不是一個(gè)好的跡象。另一方面,如果關(guān)閉的問題表明缺陷很少,并且發(fā)現(xiàn)是及時(shí)修復(fù)的,那就太好了。
2.5 維護(hù)
查看包的提交歷史,代碼被積極維護(hù)了多長時(shí)間?現(xiàn)在還在積極維護(hù)嗎?積極維護(hù)了較長時(shí)間的軟件包更有可能繼續(xù)得到維護(hù)。有多少人在包上做了提交?許多軟件包是業(yè)余時(shí)間創(chuàng)建和分享的個(gè)人項(xiàng)目,還有一些是一群付費(fèi)開發(fā)人員數(shù)千小時(shí)工作的結(jié)果。一般來說,后一種類型的軟件包更有可能迅速修復(fù)錯(cuò)誤,進(jìn)行穩(wěn)定的改進(jìn),并進(jìn)行常規(guī)維護(hù)。
2.6 用法
是否有許多其他軟件依賴于此代碼庫?依賴管理器通??梢蕴峁╆P(guān)于使用情況的統(tǒng)計(jì)數(shù)據(jù),或者可以使用搜索來評(píng)估其他人使用該包的頻率。更多的用戶至少意味著有很多人能夠很好地使用代碼,并且能夠更快地發(fā)現(xiàn)新的 bug。廣泛的使用還可以避免持續(xù)維護(hù)的問題,因?yàn)橛信d趣的用戶可能會(huì)做出更多貢獻(xiàn)。
2.7 安全性
依賴包能夠處理不可信的輸入嗎?如果是,它是否對(duì)惡意輸入具有強(qiáng)大的抵抗力?它是否有列出安全問題的歷史?例如, 流行的 PCRE 正則表達(dá)式庫有諸如緩沖區(qū)溢出等問題的歷史,特別是在其解析器中。這一發(fā)現(xiàn)并沒有立即導(dǎo)致放棄 PCRE,但它確實(shí)使我們更仔細(xì)地考慮測試和隔離。
2.8 許可證
代碼是否得到了正確的許可?它到底有沒有許可證?公司是否接受這樣的許可證?很多 GitHub 上的項(xiàng)目都沒有明確的許可證。公司可能會(huì)對(duì)依賴項(xiàng)的許可證施加進(jìn)一步的限制。例如,不允許使用類似 agpl 許可證授權(quán)的代碼,它可能過于繁瑣,也不允許使用類似 wtpl 的許可證,它可能過于模糊。
2.9 依賴的依賴
代碼庫是否有自己的依賴項(xiàng)?間接依賴關(guān)系中的缺陷與直接依賴關(guān)系中的缺陷一樣對(duì)程序不利。依賴管理器可以列出給定包的所有依賴項(xiàng),理想情況下應(yīng)該按照這里描述的方式檢查每個(gè)依賴項(xiàng)。具有許多依賴項(xiàng)的包會(huì)帶來額外的檢查工作,因?yàn)檫@些相同的依賴項(xiàng)會(huì)帶來需要進(jìn)行評(píng)估的額外風(fēng)險(xiǎn)。
許多開發(fā)人員可能從來沒有看過依賴關(guān)系的完整列表,也不知道它們依賴什么。例如,包括 Babel、 Ember 和 Reactall 在內(nèi)的許多流行項(xiàng)目間接依賴于一個(gè)名為 left-pad 的微型庫,該包由一個(gè)單獨(dú)的八行函數(shù)組成。在2016年3月,作者從 NPM 中刪除了這個(gè)包,無意中破壞了大多數(shù) Node.js 用戶的構(gòu)建。當(dāng)時(shí)的轟動(dòng)至今記憶猶新。
3. 依賴的測試
檢查過程應(yīng)該包括運(yùn)行庫自己的測試。如果庫通過了檢查,并且決定依賴于它,那么下一步應(yīng)該是編寫新的測試,重點(diǎn)是我們應(yīng)用程序所需的功能。這些測試通常以簡短的獨(dú)立程序開始,編寫這些程序是為了確保我們能夠理解庫的 API,并確保它完成預(yù)期的任務(wù)。值得付出額外的努力,將這些程序轉(zhuǎn)換為可以針對(duì)包的較新版本運(yùn)行的自動(dòng)化測試。如果發(fā)現(xiàn)了一個(gè) bug 并且有了一個(gè)潛在的修復(fù),那么希望能夠輕松地重新運(yùn)行這些特定于項(xiàng)目的測試,以確保修復(fù)沒有破壞其他任何東西,值得對(duì)基本檢查所確定的可能存在問題的領(lǐng)域進(jìn)行研究。
4. 依賴的抽象
根據(jù)庫的不同,也許更新會(huì)把軟件包帶向一個(gè)新的方向,也許會(huì)發(fā)現(xiàn)嚴(yán)重的安全問題,也許會(huì)有更好的選擇。出于所有這些原因,將項(xiàng)目輕松遷移到新的依賴項(xiàng)是值得的。
如果庫將在項(xiàng)目源代碼的許多地方使用,那么遷移到新的依賴項(xiàng)將需要對(duì)所有這些不同的源位置進(jìn)行更改。更糟糕的是,如果庫在自己項(xiàng)目的 API 中公開,那么遷移到新的依賴項(xiàng)將需要對(duì)調(diào)用API 的所有代碼進(jìn)行更改,而我們可能無法控制這些更改。為了避免這些代價(jià),有必要定義一個(gè)自己的接口,并使用依賴項(xiàng)實(shí)現(xiàn)該接口的封裝。封裝應(yīng)該只包含項(xiàng)目從依賴庫中需要的內(nèi)容,而不是依賴庫提供的所有內(nèi)容。理想情況下,這允許僅更改封裝接口來替換不同但同樣適合的依賴關(guān)系。每個(gè)項(xiàng)目的遷移到新接口時(shí),將測試封裝接口的實(shí)現(xiàn)。
這種間接性使測試備用庫變得容易,并且它防止了在源代碼樹的其余部分中意外地引入依賴庫的內(nèi)部方法。反過來,這又確保了在需要時(shí)可以輕松地切換到不同的依賴項(xiàng)。
5. 依賴的隔離
在運(yùn)行時(shí)隔離依賴項(xiàng)也可能是適當(dāng)?shù)?,以便限制錯(cuò)誤可能造成的損害。例如,Google Chrome 允許用戶在瀏覽器中添加依賴文件/擴(kuò)展代碼。因此,在一個(gè)糟糕的擴(kuò)展中,一個(gè)可利用的 bug 不能自動(dòng)訪問瀏覽器本身的整個(gè)內(nèi)存,并且可以被阻止進(jìn)行不適當(dāng)?shù)南到y(tǒng)調(diào)用。如今,隔離依賴關(guān)系可以降低運(yùn)行該代碼的相關(guān)風(fēng)險(xiǎn)。
可疑代碼的運(yùn)行時(shí)隔離是困難的,而且很少完成。真正的隔離需要一種完全內(nèi)存安全的語言,沒有非類型化的代碼。這不僅在 C和 C++ 語言中具有挑戰(zhàn)性,而且在提供受限制不安全操作的語言中也很具有挑戰(zhàn)性,例如 Java 在包含 JNI的時(shí)候,或者 Go和 Swift 在包含它們的“不安全”特性時(shí)。即使是在 JavaScript 這樣的內(nèi)存安全語言中,代碼通常也可以訪問超出其需要的內(nèi)容。針對(duì)這類問題的眾多可能防御措施之一,是更好地限制依賴。
6. 依賴的避免
如果一個(gè)依賴項(xiàng)看起來太危險(xiǎn),無法找到一種方法來隔離它,最好的答案可能是完全避免它,或者至少避免那些我們認(rèn)為最有問題的部分。
如果只需要依賴庫的一小部分,最簡單的解決方案可能是復(fù)制所需的內(nèi)容,當(dāng)然,保留適當(dāng)?shù)陌鏅?quán)和其他法律聲明。我們正在承擔(dān)修復(fù)錯(cuò)誤、維護(hù)等責(zé)任,但也完全與更大的風(fēng)險(xiǎn)隔離開來。一點(diǎn)點(diǎn)復(fù)制總比一點(diǎn)點(diǎn)依賴要好。
7. 依賴的升級(jí)
升級(jí)帶來了引入新 bug 的機(jī)會(huì),如果沒有相應(yīng)的回報(bào),為什么要冒這個(gè)風(fēng)險(xiǎn)呢?這種分析忽略了兩個(gè)成本。首先是最終升級(jí)的成本。在軟件方面,代碼更改的難度不是線性的,做10個(gè)小更改比做一個(gè)等價(jià)的大更改更簡單,也更容易做對(duì)。第二個(gè)問題是發(fā)現(xiàn)已修復(fù)bug 的代價(jià)。特別是在安全場景中,已知的錯(cuò)誤可能會(huì)被利用,可能是攻擊者的闖入。
及時(shí)升級(jí)是很重要的,但這意味著向項(xiàng)目中添加新的代碼,這意味著要更新新版本依賴庫的風(fēng)險(xiǎn)評(píng)估。至少,需要瀏覽從當(dāng)前版本到升級(jí)版本的變更差異,或者至少閱讀發(fā)布文檔,以確定升級(jí)代碼中可能需要關(guān)注的領(lǐng)域。如果許多代碼正在更改,以致難以消化,那么可以將這種情況納入風(fēng)險(xiǎn)評(píng)估。
重新運(yùn)行依賴庫自己的測試也是有意義的。如果它具有自己的依賴項(xiàng),那么項(xiàng)目的配置完全有可能使用與庫作者使用的不同版本依賴項(xiàng)。運(yùn)行庫自己的測試可以快速識(shí)別特定于配置的問題。同樣,升級(jí)不應(yīng)該是完全自動(dòng)的。在部署升級(jí)版本之前,必須驗(yàn)證它們是否適合自己的環(huán)境。
在大多數(shù)情況下,延遲升級(jí)比快速升級(jí)的風(fēng)險(xiǎn)更大。
8. 依賴的關(guān)注
重要的是要持續(xù)關(guān)注,甚至可能重新評(píng)估使用它們的決定。
首先,確保使用我們所認(rèn)為的特定庫版本?,F(xiàn)在,大多數(shù)依賴管理器可以輕松記錄給定庫版本預(yù)期源碼的加密哈希值,然后在另一臺(tái)計(jì)算機(jī)或測試環(huán)境中重新下載這個(gè)庫時(shí)檢查這個(gè)哈希。這可以確保使用與我們檢查測試時(shí)相同的依賴源碼。
同樣重要的是,要注意新的間接依賴關(guān)系是否會(huì)爬進(jìn)來。升級(jí)可以很容易地引入新的包,而我們的項(xiàng)目現(xiàn)在依賴于這些包。它們也是值得關(guān)注的,惡意代碼可能被隱藏在一個(gè)不同的包中。依賴關(guān)系還會(huì)影響項(xiàng)目的大小。
升級(jí)是重新考慮使用依賴項(xiàng)的自然時(shí)機(jī),定期重新審視依賴關(guān)系也很重要。這個(gè)項(xiàng)目被放棄了嗎?也許是時(shí)候開始計(jì)劃取代這種依賴性了。
9. 依賴,該說不該說的
軟件復(fù)用好處不應(yīng)被低估,依賴關(guān)系比以往任何時(shí)候都多,它給軟件開發(fā)人員帶來了積極的轉(zhuǎn)變。即便如此,我們卻沒有完全考慮到潛在的后果。
- 關(guān)于軟件依賴有三個(gè)主要的建議:
- 認(rèn)識(shí)到問題,我們需要集中精力來解決這個(gè)問題。
- 為今天建立最佳實(shí)踐,需要最佳實(shí)踐來使用依賴關(guān)系的管理。這意味著制定從決策到評(píng)估,以及減少和跟蹤風(fēng)險(xiǎn)的過程。事實(shí)上,正如工程師專注于測試一樣,有些人可能需要專注于管理依賴關(guān)系。
- 為明天開發(fā)更好的依賴技術(shù)。依賴管理器基本上消除了下載和安裝的成本。未來的開發(fā)工作應(yīng)該側(cè)重于降低使用依賴項(xiàng)所必需的評(píng)估和維護(hù)成本。構(gòu)建工具至少應(yīng)該使運(yùn)行依賴庫自己的測試變得容易,還應(yīng)該提供簡單的方法來隔離可疑的依賴庫。
對(duì)特定依賴關(guān)系的嚴(yán)格檢查需要大量工作,并且仍然有例外出現(xiàn)。對(duì)于每一個(gè)可能的新依賴,不太可能有開發(fā)人員真正付出這樣的努力,盡管文中給出的可能只是一個(gè)子集。
分享名稱:軟件依賴的一知半解
鏈接URL:http://m.fisionsoft.com.cn/article/djojdih.html


咨詢
建站咨詢
