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

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


咨詢
建站咨詢
