几十年来,关于软件复用的讨论远比实际的软件复用更为常见。而今,情况已然逆转:开发者们每天都在以软件依赖的形式复用他人编写的软件,而这种情况却大多未受审视。
我的背景包括在谷歌内部源代码系统工作十年的经验,该系统将软件依赖视为首要概念17,以及开发 Go 编程语言中对依赖的支持2。
软件依赖带来了严重的风险,而这些风险往往被忽视。向便捷、细粒度的软件复用的转变发生得如此之快,以至于我们尚未理解有效选择和使用依赖的最佳实践,甚至还未确定何时适合使用依赖,何时不适合。本文的目的是提高对风险的认识,并鼓励对解决方案进行更多调查。
在当今的软件开发世界中,依赖是指程序员想要调用的额外代码。添加依赖可以避免重复已完成的工作:设计、编写、测试、调试和维护特定的代码单元。在本文中,这个代码单元被称为包;一些系统使用术语库和模块来代替。
采用外部编写的依赖项是一种由来已久的实践。大多数程序员在其职业生涯的某个时刻都不得不经历手动下载和安装所需库的步骤,例如 C 语言的 PCRE 或 zlib;C++ 的 Boost 或 Qt;或者 Java 的 JodaTime 或 JUnit。这些包包含高质量、经过调试的代码,这些代码需要大量的专业知识才能开发出来。对于需要其中一个包提供的功能的程序来说,手动下载、安装和更新包的繁琐工作比从头重新开发该功能的工作要容易。然而,复用的高固定成本意味着手动复用的包往往很大;一个很小的包更容易重新实现。
依赖管理器(有时称为包管理器)自动化了依赖包的下载和安装。由于依赖管理器使下载和安装单个包变得更容易,因此较低的固定成本使得发布和复用较小的包在经济上可行。
例如,Node.js 依赖管理器 NPM(Node 包管理器)提供了对超过 750,000 个包的访问。其中一个包 escape-string-regexp
仅包含一个函数,该函数转义其输入中的正则表达式运算符。整个实现是
var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
module.exports = function (str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.replace(matchOperatorsRe, '\\$&');
};
在依赖管理器出现之前,发布一个八行代码的库是不可想象的:对于如此小的收益来说,开销太大了。然而,NPM 将开销几乎降至零,结果是,几乎微不足道的功能也可以被打包和复用。在 2019 年 4 月下旬,escape-string-regexp
包被近一千个其他 NPM 包显式依赖,更不用说开发者为自己使用而编写且未共享的所有包了。
现在,几乎每种编程语言都存在依赖管理器:Maven Central (Java)、NuGet (.NET)、Packagist (PHP)、PyPI (Python) 和 RubyGems (Ruby) 各自托管超过 100,000 个包。这种细粒度、广泛的软件复用的到来是过去二十年软件开发中最具影响力的转变之一。如果我们不更加小心,它将导致严重的问题。
对于本文的讨论,包是从互联网下载的代码。将包添加为依赖项会将开发该代码的工作(设计、编写、测试、调试和维护)外包给互联网上的其他人,这个人通常是程序员不认识的。使用该代码会将程序暴露于依赖项中的所有故障和缺陷。程序的执行现在实际上依赖于从互联网上的陌生人下载的代码。这样来看,这听起来非常不安全。为什么会有人这样做呢?
因为这很容易,看起来有效,其他所有人都在这样做,而且,最重要的是,这看起来像是古老的既定实践的自然延续。但是,有一些重要的差异被忽略了。
几十年前,大多数开发者信任他人编写他们所依赖的软件,例如操作系统和编译器。这些软件是从已知的来源购买的,通常带有某种支持协议。仍然存在错误或公然恶意行为的可能性19,但至少开发者知道他们在与谁打交道,并且通常可以获得商业或法律追索权。
通过互联网免费分发的开源软件现象已经取代了许多早期的软件购买。当复用很困难时,发布可复用代码包的项目较少。即使他们的许可证通常声明,除其他外,任何“适销性和特定用途适用性的默示保证”,这些项目也建立了良好的声誉,这通常在人们决定使用哪个项目时起着重要作用。对软件来源的信任的商业和法律支持被声誉支持所取代。许多常见的早期包仍然享有良好的声誉:例如 BLAS(1979 年发布)、Netlib(1987 年)、libjpeg(1991 年)、LAPACK(1992 年)、HP STL(1994 年)和 zlib(1995 年)。
依赖管理器缩小了这种开源代码复用模型的规模。现在,开发者可以共享粒度为数十行代码的单个函数的代码。这是一项重大的技术成就。无数的包可用,编写代码可能涉及大量的包,但是信任代码的商业、法律和声誉支持机制并没有延续下来。开发者正在信任更多的代码,而这样做的理由却越来越少。
采用不良依赖项的成本可以视为所有可能的坏结果的总和,即每个坏结果的成本乘以其发生的概率(风险),如公式所示。
依赖项将被使用的上下文决定了坏结果的成本。在一个极端,是一个个人爱好项目,其中大多数坏结果的成本接近于零:你只是在玩乐,错误除了浪费时间之外没有实际影响,即使调试也可能很有趣。因此,风险概率几乎无关紧要——它乘以几乎为零的失败成本。在另一个极端,是必须维护多年的生产软件。在这里,依赖项中的错误的成本可能非常高:服务器可能会宕机,敏感数据可能会泄露,客户可能会受到伤害,公司可能会倒闭。高失败成本使得估计然后降低严重失败的任何风险变得更加重要。
无论预期成本如何,与较大依赖项的经验都表明了一些估计和降低添加软件依赖项风险的方法。可能需要更好的工具来帮助降低这些方法的成本,就像依赖管理器迄今为止专注于降低下载和安装成本一样。
你不会雇用一个你从未听说过并且一无所知的软件开发者。你首先会更多地了解这个人:查看推荐信、进行面试、进行背景调查等等。在你依赖于在互联网上找到的包之前,同样明智的做法是首先了解一下它。
基本的检查可以让你了解你在尝试使用此代码时遇到问题的可能性有多大。如果检查显示可能存在较小的问题,你可以采取措施为这些问题做好准备,或者可能避免它们。如果检查显示存在主要问题,最好不要使用该包;也许你会找到更合适的包,或者也许你需要自己开发一个。请记住,开源包是由其作者发布的,希望它们有用,但不保证可用性或支持。在生产中断期间,你将是调试包的人。正如最初的 GNU 通用公共许可证警告的那样,“关于程序的质量和性能的全部风险由你承担。如果程序被证明有缺陷,你将承担所有必要的维修、修理或纠正的费用。”7
本节的其余部分概述了在检查包并决定是否依赖它时的一些考虑因素。
文档是否清晰?API 是否具有清晰的设计?如果作者可以在文档中很好地解释包的 API 及其设计,那么他们很可能在源代码中也向计算机很好地解释了实现。使用清晰、设计良好的 API 编写代码也更容易、更快,并且有望减少错误。作者是否记录了他们对客户端代码的期望,以便使未来的升级兼容?(示例包括 C++23 和 Go8 兼容性文档。)
代码是否编写良好?阅读一些代码。看起来作者是否认真、尽责且前后一致?看起来像你想调试的代码吗?你可能需要调试。
开发你自己的系统化方法来检查代码质量。例如,像使用重要的编译器警告启用(例如,-Wall
)编译 C 或 C++ 程序这样简单的操作,就可以让你了解开发者在避免各种未定义行为方面有多么认真。最近的语言(如 Go、Rust 和 Swift)使用 unsafe 关键字来标记违反类型系统的代码;查看有多少不安全代码。更高级的语义工具(如 Infer6 或 SpotBugs20)也很有帮助。Linters 的帮助较小:你应该忽略关于诸如大括号样式之类的主题的死记硬背的建议,而应专注于语义问题。
对不熟悉的开发实践保持开放的心态。例如,SQLite 库以单个 200,000 行的 C 源代码文件和单个 11,000 行的头文件(称为amalgamation)的形式发布。这些文件的庞大体积应该引起最初的警惕,但更仔细的调查会发现实际的开发源代码、一个传统的包含 100 多个 C 源代码文件、测试和支持脚本的文件树。事实证明,单文件分发是从原始源文件自动构建的,并且对于最终用户来说更容易,特别是对于那些没有依赖管理器的用户。(编译后的代码也运行得更快,因为编译器可以看到更多的优化机会。)
代码是否有测试?你可以运行它们吗?它们通过了吗?测试确立了代码的基本功能是正确的,并且它们表明开发者认真对待保持其正确性。例如,SQLite 开发树具有极其彻底的测试套件,包含超过 30,000 个单独的测试用例,以及解释测试策略的开发者文档10。另一方面,如果测试很少或没有测试,或者测试失败,那是一个严重的警示信号。对包的未来更改很可能会引入本可以轻易捕获的回归。如果你坚持在你编写的代码中进行测试(你是这样做的,对吧?),那么你应该坚持在你外包给其他人的代码中进行测试。
假设测试存在、运行并通过,你可以通过使用运行时检测(如代码覆盖率分析、竞争检测16、内存分配检查和内存泄漏检测)来运行它们,从而收集更多信息。
找到包的问题跟踪器。是否有许多未解决的错误报告?它们已经打开多久了?是否有许多已修复的错误?最近是否修复了任何错误?如果你看到大量关于看起来像是真实错误的未解决问题,尤其是如果它们已经打开了很长时间,那不是一个好兆。另一方面,如果已关闭的问题表明很少发现错误并且能及时修复,那就太好了。
查看包的提交历史记录。代码已经活跃维护了多久?现在是否还在积极维护?长期积极维护的包更有可能继续得到维护。有多少人参与包的开发?许多包是个人项目,开发者创建它们并在业余时间为了乐趣而共享。其他包是由一群付费开发者花费数千小时工作的结果。一般来说,后一种包更有可能及时进行错误修复、稳定改进和一般维护。
另一方面,有些代码确实是“完成”了。例如,前面显示的 NPM 的 escape-string-regexp
可能永远不需要再次修改。
是否有许多其他包依赖于此代码?依赖管理器通常可以提供有关使用情况的统计信息,或者你可以使用网络搜索来估计其他人编写关于使用该包的频率。更多的用户至少应该意味着更多的人的代码能够正常工作,以及更快地检测到新的错误。广泛的使用也是对持续维护问题的对冲;如果一个广泛使用的包失去了维护者,很可能会有感兴趣的用户站出来。
例如,像 PCRE 或 Boost 或 JUnit 这样的库被极其广泛地使用。这使得你可能遇到的错误更有可能(当然不是保证)已经被修复,因为其他人先遇到了它们。
你将使用该包处理不受信任的输入吗?如果是这样,它看起来是否能够可靠地抵抗恶意输入?它在 NVD(国家漏洞数据库)13 中是否有安全问题历史记录?
例如,在 2006 年,当 Jeff Dean 和我开始研究 Google 代码搜索5(在公共源代码中进行 grep)时,流行的 PCRE 正则表达式库似乎是一个显而易见的选择。然而,在与 Google 安全团队的早期讨论中,我们了解到 PCRE 有缓冲区溢出等问题历史,尤其是在其解析器中。我们可以通过在 NVD 中搜索 PCRE 来了解同样的情况。这一发现并没有立即导致我们放弃 PCRE,但它确实让我们更加仔细地考虑测试和隔离。
代码是否已正确许可?它是否有许可证?许可证对于你的项目或公司是否可以接受?GitHub 上令人惊讶的一部分项目没有明确的许可证。你的项目或公司可能会对依赖项的允许许可证施加进一步的限制。例如,Google 不允许使用在 AGPL 类许可证(过于繁琐)以及 WTFPL 类许可证(过于模糊)下许可的代码9。
代码本身是否有依赖项?间接依赖项中的缺陷与直接依赖项中的缺陷对你的程序同样有害。依赖管理器可以列出给定包的所有传递依赖项,并且理想情况下,应该按照本节中的描述检查每个依赖项。具有许多依赖项的包会产生额外检查工作,因为这些相同的依赖项会产生需要评估的额外风险。
许多开发者从未看过其代码的完整传递依赖项列表,也不知道他们依赖什么。例如,在 2016 年 3 月,NPM 用户社区发现,许多流行的项目(包括 Babel、Ember 和 React)都间接依赖于一个名为 left-pad
的微小包,该包仅包含一个八行函数体。当 left-pad
的作者从 NPM 中删除该包时,他们发现了这一点,无意中破坏了大多数 Node.js 用户的构建22。而且 left-pad
在这方面绝非例外。例如,在 NPM 上发布的 750,000 个包中,有 30% 的包(至少间接)依赖于 escape-string-regexp
。借用 Leslie Lamport 关于分布式系统的观察,依赖管理器很容易创建一个局面,在这种局面下,你甚至不知道存在的包的失败可能会使你自己的代码无法使用。
检查过程应包括运行包自己的测试。如果包通过检查,并且你决定使你的项目依赖于它,那么下一步应该是编写新的测试,重点关注你的应用程序所需的功能。这些测试通常从编写的简短独立程序开始,以确保你可以理解包的 API,并且它能够按你认为的方式工作。(如果你不能或它不能,现在就回头!)值得付出额外的努力将这些程序变成自动化测试,这些测试可以针对较新版本的包运行。如果你发现了一个错误并且有一个潜在的修复程序,你将希望能够轻松地重新运行这些项目特定的测试,以确保该修复程序没有破坏任何其他内容。
尤其值得对基本检查确定的可能存在问题的区域进行练习。对于代码搜索,我们从过去的经验中知道,PCRE 有时会花费很长时间来执行某些正则表达式搜索。最初的计划是为“简单”和“复杂”正则表达式搜索分别设置线程池。第一个测试之一是将 pcregrep
与其他一些 grep
实现进行比较的基准测试。对于一个基本测试用例,pcregrep
比可用的最快的 grep
慢 70 倍,因此我们开始重新考虑使用 PCRE 的计划。即使 PCRE 最终被完全放弃,该基准测试今天仍然保留在代码库中。
依赖于包是一个以后可能会重新审视的决定。也许更新会将包带向新的方向。也许会发现严重的安全问题。也许会出现更好的选择。由于所有这些原因,值得努力使你的项目易于迁移到新的依赖项。
如果包将从你的项目源代码中的许多地方使用,则迁移到新的依赖项将需要更改所有这些不同的源代码位置。更糟糕的是,如果包将在你自己的项目 API 中公开,则迁移到新的依赖项将需要更改所有调用你的 API 的代码,而你可能无法控制这些代码。为了避免这些成本,定义你自己的接口,以及使用依赖项实现该接口的瘦包装器是有意义的。请注意,包装器应仅包含你的项目从依赖项中需要的内容,而不是依赖项提供的所有内容。理想情况下,这允许你稍后通过仅更改包装器来替换不同的、同样合适的依赖项。将你的每个项目的测试迁移到使用新接口将测试接口和包装器实现,并使其易于测试依赖项的任何潜在替代品。
对于代码搜索,我们开发了一个抽象的 Regexp
类,该类定义了代码搜索从任何正则表达式引擎中需要的接口。然后,我们编写了一个围绕 PCRE 的瘦包装器来实现该接口。这种间接性使得测试备用库变得容易,并且它防止了意外地将 PCRE 内部知识引入到源代码树的其余部分。反过来,这确保了如果需要,可以轻松地切换到不同的依赖项。
为了限制错误可能造成的损害,在运行时隔离依赖项也可能是合适的。例如,Google Chrome 允许用户向浏览器添加依赖项(扩展代码)。当 Chrome 在 2008 年推出时,它引入了关键功能(现在已成为所有浏览器的标准功能),即将每个扩展隔离在单独的操作系统进程中运行的沙箱中18。
因此,编写不良的扩展中的可利用错误不会自动访问浏览器本身的整个内存,并且可以阻止其进行不适当的系统调用12。对于代码搜索,在我们完全放弃 PCRE 之前,计划是将至少 PCRE 解析器隔离在类似的沙箱中。今天,另一种选择将是基于轻量级虚拟机监控程序的沙箱,例如 gVisor11。隔离依赖项降低了运行该代码的相关风险。
即使有这些示例和其他现成的选项,可疑代码的运行时隔离仍然过于困难,并且很少完成。真正的隔离需要一种完全内存安全的语言,并且没有进入非类型化代码的后门。这不仅在 C 和 C++ 等完全不安全的语言中具有挑战性,而且在提供受限的不安全操作的语言中也具有挑战性,例如包含 JNI(Java 本地接口)时的 Java,或者包含其“不安全”功能的 Go、Rust 和 Swift。即使在 JavaScript 这样的内存安全语言中,代码通常也具有远远超出其需要的访问权限。在 2018 年 11 月,最新版本的 NPM 包 event-stream
(为 JavaScript 事件提供功能性流式 API)被发现包含混淆的恶意代码,该代码是在两个半月前添加的。该代码从 Copay 移动应用程序的用户那里收集大量比特币钱包,访问了与处理事件流完全无关的系统资源1。对此类问题的众多可能的防御措施之一是更好地限制依赖项可以访问的内容。
如果依赖项看起来风险太大,并且你找不到隔离它的方法,那么最好的答案可能是完全避免它,或者至少避免你已确定为最有问题的部分。
例如,随着我们更好地理解与 PCRE 相关的风险和成本,我们为 Google 代码搜索制定的计划从“直接使用 PCRE”演变为“使用 PCRE 但对解析器进行沙箱处理”,再到“编写新的正则表达式解析器但保留 PCRE 执行引擎”,再到“编写新的解析器并将其连接到不同的、更高效的开源执行引擎”。后来,我们也重写了执行引擎,以便不留下任何依赖项,并将结果开源:RE24。
如果你只需要依赖项的一小部分,最简单的解决方案可能是复制你需要的代码(当然,要保留适当的版权和其他法律声明)。你正在承担修复错误、维护等的责任,但你也完全隔离于更大的风险。Go 开发者社区对此有一句谚语:“少量复制胜过少量依赖。”14
长期以来,关于软件的传统智慧是,“如果没坏,就不要修”。升级有可能引入新的错误;如果没有相应的奖励(例如你需要的新功能),为什么要承担风险?这种分析忽略了两个成本。第一个是最终升级的成本。在软件中,进行代码更改的难度不会线性扩展:进行十次小的更改比进行一次等效的大更改的工作量更少,并且更容易正确。第二个是艰难地发现已修复的错误的成本。尤其是在安全环境中,已知错误会被积极利用,你等待的每一天都是攻击者可能闯入的另一天。
例如,考虑一下 2017 年 Equifax 发生的事情,正如高管在详细的国会证词中叙述的那样21。3 月 7 日,Apache Struts 中的一个新的漏洞被披露,并发布了补丁版本。3 月 8 日,Equifax 收到了 US-CERT(美国计算机应急响应小组)关于需要更新 Apache Struts 的任何使用的通知。Equifax 分别在 3 月 9 日和 3 月 15 日进行了源代码和网络扫描;两次扫描都没有发现特定的一组面向公众的 Web 服务器。5 月 13 日,攻击者找到了 Equifax 安全团队找不到的服务器。他们利用 Apache Struts 漏洞入侵了 Equifax 的网络,然后在接下来的两个月中窃取了 1.48 亿人的详细个人和财务信息。Equifax 最终在 7 月 29 日注意到了违规行为,并在 9 月 4 日公开披露了该行为。到 9 月底,Equifax 的 CEO、CIO 和 CSO 都已辞职,并且国会调查正在进行中。
Equifax 的经验有力地说明,尽管依赖管理器在构建时知道它们正在使用的版本,但必须做出其他安排以在生产部署过程中跟踪该信息。对于 Go 语言,我们正在试验在每个二进制文件中自动包含版本清单,以便部署过程可以扫描二进制文件以查找需要升级的依赖项。Go 还可以在运行时提供该信息,以便服务器可以查阅已知错误的数据库,并在需要升级时向监控软件自我报告。
及时升级很重要,但这意味着向你的项目添加新代码,这应该意味着根据新版本更新你对使用依赖项的风险评估。至少,你将需要浏览差异,显示从当前版本到升级版本的更改,或者至少阅读发行说明,以确定升级代码中最有可能引起关注的区域。如果更改了很多代码,以至于差异难以消化,你可以将这一事实纳入你的风险评估更新中。
你还需要重新运行你编写的特定于你的项目的测试,以确保升级后的包至少与早期版本一样适合该项目。重新运行包自己的测试也是有意义的。如果包有自己的依赖项,则你的项目配置完全有可能使用与包作者使用的依赖项版本不同的版本(无论是旧版本还是新版本)。运行包自己的测试可以快速识别特定于你的配置的问题。
同样,升级不应完全自动化。在部署升级后的版本之前,你需要验证这些版本是否适合你的环境3。
如果你的升级过程包括重新运行你已为依赖项编写的集成和资格测试,以便你很可能在问题到达生产环境之前识别出新问题,那么在大多数情况下,延迟升级比快速升级风险更大。
安全关键型升级的窗口尤其短暂。在 Equifax 违规事件之后,法证安全团队发现了证据,表明攻击者(可能是不同的攻击者)在 3 月 10 日(即公开披露后的仅仅三天)成功利用了受影响服务器上的 Apache Struts 漏洞,但他们只运行了一个 whoami
命令。
即使在完成所有这些工作之后,你还没有完成对依赖项的管理。继续监视它们,甚至可能重新评估你使用它们的决定,这一点很重要。
首先,确保你继续使用你认为正在使用的特定包版本。大多数依赖管理器现在可以轻松甚至自动地记录给定包版本的预期源代码的加密哈希,然后在另一台计算机或测试环境中重新下载包时检查该哈希。这确保了你的构建使用你检查和测试过的相同的依赖项源代码。这些类型的检查阻止了前面描述的 event-stream
攻击者在已发布的 3.3.5 版本中静默插入恶意代码。相反,攻击者不得不创建一个新版本 3.3.6,并等待人们升级(而没有仔细查看更改)。
监视新的间接依赖项的潜入也很重要。升级很容易引入新的包,你的项目的成功现在依赖于这些包。它们也值得你关注。在 event-stream
的案例中,恶意代码隐藏在不同的包 flatmap-stream
中,这是新的 event-stream
版本添加的新依赖项。
潜入的依赖项也会影响你的项目的大小。在 Google 的 Sawzall15(一种 JIT 编译的日志处理语言)的开发过程中,作者在不同时间发现,主解释器二进制文件不仅包含 Sawzall 的 JIT,还包含(未使用的)PostScript、Python 和 JavaScript 解释器。每次,罪魁祸首都被证明是 Sawzall 确实依赖的某个库声明的未使用依赖项,再加上 Google 的构建系统消除了启动使用新依赖项所需的任何手动工作。这种错误是 Go 语言将导入未使用的包设为编译时错误的原因。
升级是重新审视使用正在更改的依赖项的决定的自然时机。定期重新审视任何没有更改的依赖项也很重要。似乎没有安全问题或其他错误需要修复是否合理?项目是否已被放弃?也许是时候开始计划替换该依赖项了。
重新检查每个依赖项的安全历史记录也很重要。例如,Apache Struts 在 2016 年、2017 年和 2018 年披露了不同的主要远程代码执行漏洞。即使你有一个运行它的所有服务器的列表并及时更新它们,该跟踪记录也可能使你重新考虑是否完全使用它。
软件复用终于到来了,其好处不应被低估。它为软件开发者带来了巨大的积极转变。即便如此,我们接受了这种转变,但没有完全思考潜在的后果。信任依赖项的旧理由正变得越来越无效,而与此同时,依赖项比以往任何时候都多。
本文概述的对特定依赖项进行批判性检查的工作量很大,并且仍然是例外而不是规则。任何开发者都不太可能真正努力为每个可能的新依赖项做到这一点。我只对我的部分依赖项做了其中的一部分。大多数时候,整个决定是,“让我们看看会发生什么”。通常,任何超出此范围的工作似乎都太费力了。
Copay 和 Equifax 攻击是当今软件依赖项使用方式中真实问题的明确警告。我们不应忽视这些警告。以下是三项广泛的建议。
1. 认识到问题。如果说这篇文章还有其他作用,那就是希望它让你相信这里有一个值得解决的问题。我们需要很多人将大量精力集中在解决这个问题上。
2. 建立今天的最佳实践。需要最佳实践来管理使用当今可用的工具的依赖项。这意味着制定评估、降低和跟踪风险的流程,从最初的采用决策到生产使用。事实上,正如一些工程师专注于测试一样,其他人可能需要专注于管理依赖项。
3. 改进未来的依赖技术。依赖管理器从根本上消除了下载和安装依赖项的成本。未来的开发工作应侧重于降低使用依赖项所需的评估和维护成本。例如,软件包发现站点可以努力寻找更多方法,让开发者分享他们的发现。构建工具至少应该让运行软件包自身的测试变得容易。更积极地,构建工具和软件包管理系统也可以协同工作,允许软件包作者针对其 API 的所有公共客户端测试新的更改。语言也应该提供简单的方法来隔离可疑的软件包。
市面上有很多优秀的软件。让我们共同努力,找出如何安全地重用它们。
1. Baldwin, A. 2018. 关于 event-stream 事件的详情。《npm 博客》(11 月);https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident。
2. Cox, R. 2018. Go 语言与版本控制;https://research.swtch.com/vgo。
3. Cox, R. 2018. Go 语言版本控制的原则。《GopherCon 新加坡》(5 月);https://www.youtube.com/watch?v=F8nrpe0XWRg。
4. Cox, R. 2010. RE2:正则表达式匹配的原理性方法。《Google 开源博客》(3 月);https://opensource.googleblog.com/2010/03/re2-principled-approach-to-regular.html。
5. Cox, R. 2012. 使用三元索引的正则表达式匹配或 Google 代码搜索的工作原理。Swtch.com(1 月);https://swtch.com/~rsc/regexp/regexp4.html。
6. Facebook。Infer:一种在 Java 和 C/C++/Objective-C 代码发布前检测 bug 的工具;https://fbinfer.com/。
7. GNU 项目。1989. GNU 通用公共许可证,版本 1;https://gnu.ac.cn/licenses/old-licenses/gpl-1.0.html。
8. Go 项目。2013. Go 1 及 Go 程序的未来;https://golang.ac.cn/doc/go1compat。
9. Google 开源。使用第三方许可证;https://opensource.google.com/docs/thirdparty/licenses/#banned。
10. Hipp, D. R. SQLite 是如何测试的;https://www.sqlite.org/testing.html。
11. Lacasse, N., 2018. 开源 gVisor,一种沙盒容器运行时。《Google Cloud》(5 月);https://cloud.google.com/blog/products/gcp/open-sourcing-gvisor-a-sandboxed-container-runtime。
12. Langley, A. 2009. Chromium 的 seccomp 沙箱。ImperialViolet(8 月);https://www.imperialviolet.org/2009/08/26/seccomp.html。
13. 美国国家标准与技术研究院。国家漏洞数据库 - 搜索和统计;https://nvd.nist.gov/vuln/search。
14. Pike, R. 2015. Go 谚语;https://go-proverbs.github.io/。
15. Pike, R., Dorward, S., Griesemer, R., Quinlan, S. 2005. 解读数据:使用 Sawzall 进行并行分析。《Scientific Programming Journal 13》(4), 277-298;https://doi.org/10.1155/2005/962135。
16. Potapenko, A. 2014. 测试 Chromium:ThreadSanitizer v2,下一代数据竞争检测器。《Chromium 博客》(4 月);https://blog.chromium.org/2014/04/testing-chromium-threadsanitizer-v2.html。
17. Potvin, R., Levenberg, J. 2016. 为什么 Google 将数十亿行代码存储在单个代码仓库中。《Communications of the 59》(7), 78-87;https://doi.org/10.1145/2854146。
18. Reis, C. 2008. 多进程架构。《Chromium 博客》(9 月);https://blog.chromium.org/2008/09/multi-process-architecture.html。
19. Thompson, K. 1984. 关于信任信任的反思。《Communications of the 27》(8), 761-763;https://doi.org/10.1145/358198.358210。
20. SpotBugs:查找 Java 程序中的 bug;https://spotbugs.github.io/。
21. 美国众议院监督与政府改革委员会。2018. Equifax 数据泄露事件,多数派工作人员报告,第 115 届国会(12 月)。
22. Willis, N. 2016. 单点故障。LWN.net(3 月);https://lwn.net/Articles/681410/。
23. Winters, T. 2018. SD-8:标准库兼容性,C++ 标准文档;https://isocpp.org/std/standing-documents/sd-8-standard-library-compatibility。
服务可用性的计算
你的可用性与你的依赖项的总和一样。
Ben Treynor、Mike Dahlin、Vivek Rau 和 Betsy Beyer
https://queue.org.cn/detail.cfm?id=3096459
跟踪和控制微服务依赖项
依赖管理是系统和软件设计的关键部分。
Silvia Esparrachiari、Tanya Reilly 和 Ashleigh Rentz
https://queue.org.cn/detail.cfm?id=3277541
你不应依赖我
纵观野外的 JavaScript 库
Tobias Lauinger、Abdelberi Chaabane 和 Christo B. Wilson
https://queue.org.cn/detail.cfm?id=3205288
Russ Cox 领导 Google 的 Go 编程语言开发,目前专注于提高使用软件依赖项的安全性和可靠性。他与 Jeff Dean 一起创建了 Google 代码搜索,让开发者可以 grep 全世界的公共源代码。他曾在贝尔实验室的 Plan 9 操作系统上工作多年,并拥有哈佛大学和麻省理工学院的学位。
版权 © 2019 由所有者/作者持有。出版权已授权给 。
最初发表于 Queue vol. 17, no. 2—
在 数字图书馆 中评论这篇文章
Catherine Hayes, David Malone - 质疑评估非加密哈希函数的标准
虽然加密和非加密哈希函数无处不在,但在它们的设计方式上似乎存在差距。加密哈希存在许多由各种安全要求驱动的标准,但在非加密方面,存在一定程度的传统,尽管哈希函数历史悠久,但尚未得到充分探索。虽然针对现实世界数据集的均匀分布很有意义,但在面对具有特定模式的数据集时,这可能是一个挑战。
Nicole Forsgren, Eirini Kalliamvakou, Abi Noda, Michaela Greiler, Brian Houck, Margaret-Anne Storey - DevEx 行动
随着领导者寻求在财政紧缩和人工智能等变革性技术的背景下优化软件交付,DevEx(开发者体验)在许多软件组织中越来越受到关注。技术领导者凭直觉接受,良好的开发者体验能够实现更有效的软件交付和开发者幸福感。然而,在许多组织中,旨在改进 DevEx 的拟议举措和投资难以获得支持,因为业务利益相关者质疑改进的价值主张。
João Varajão, António Trigo, Miguel Almeida - 低代码开发生产力
本文旨在通过展示使用基于代码、低代码和极端低代码技术进行的实验室实验结果来研究生产力差异,从而为该主题提供新的见解。低代码技术已清晰地显示出更高的生产力水平,为低代码在短期/中期内主导软件开发主流提供了强有力的论据。本文报告了程序和协议、结果、局限性和未来研究的机会。
Ivar Jacobson, Alistair Cockburn - 用例至关重要
虽然软件行业是一个快节奏且令人兴奋的世界,其中不断开发新的工具、技术和方法来服务于商业和社会,但它也很健忘。在其快速前进的过程中,它容易受到时尚的支配,并且可能会忘记或忽略某些它面临的永恒问题的已被证明的解决方案。用例,最早于 1986 年引入并在后来普及,就是这些已被证明的解决方案之一。