包管理器和依赖项的责任

自动依赖解决的潜在危险

image.png|300

注:本文核心内容由大语言模型生成,辅以人工事实核查与结构调整。

将包管理器(Package Managers,简称 PM)称为“邪恶”的工具——这一说法带有一定的夸张成分,用于强调问题——主要是在质疑现代编程中过度依赖这些自动化工具。这种观点认为,虽然包管理器无疑提供了便利,但它们也带来了严重的负担,因为它们自动化了本应需要仔细思考和人工把控的过程。

核心论点:依赖地狱的自动化

对包管理器的主要批评在于,它们让开发者过于轻易地陷入依赖地狱。包管理器的职能是:从仓库下载包,处理依赖关系,并递归地下载依赖的依赖。

在大型项目中,依赖地狱是一个真实存在的问题:一个代码库可能会积累数千,甚至上万个依赖。这种情况下,开发者往往不知道这些依赖是如何工作的,bug 藏在哪里,或者这些依赖是如何被管理的。

问题并不是依赖地狱本身的存在,而是包管理器让人们进入这种状态过于轻松和迅速,从而削弱了开发者进行审慎决策的动力。

“你只需轻轻敲击命令行,比如 npm installgo get,就会瞬间坠入复杂的依赖地狱。”

包管理器所带来的不必要的自动化,消除了本该存在的“摩擦”。这种摩擦原本能迫使开发者反思他们的选择。当开发者必须手动下载并整合一个库时,他们会被迫去想:“也许我并不需要它” 或者 “也许我可以用别的方法实现”。而在手动更新时,开发者也必须非常小心。

1
2
3
4
5
6
7
# 自动化流程(过于轻松,消除了思考的摩擦)
go get github.com/some/large/framework

# 手动流程迫使开发者思考与取舍
# 1. 评估是否真的需要
# 2. 决定手动复制/内嵌
# 3. 固定具体版本(需要谨慎)

区分(并不是问题的根源)

需要特别强调,批评的矛头仅指向包管理器本身(即自动化的依赖解析),而不是以下概念:

  • 包(Packages): 代码组织的基本单元。例如 Odin 语言内置了包的概念。

  • 包仓库(Package Repositories): 用于发现和存储包的地方,本质上类似搜索引擎。

  • 构建系统(Build Systems): 通常是语言专用的工具,用于编译和链接代码。像 Odin 这样的语言对独立构建系统的需求较少,因为大多数项目只需通过 odin build . 即可构建,链接信息由源码中的 foreign 系统直接定义。

1
2
3
4
5
6
7
// 示例:一个依赖于源码内定义链接信息的最小化构建过程
// 构建系统本身不是问题,真正的问题在于依赖的自动化管理。
foreign module_dependency {
// 链接信息在源码中定义,而不是交由包管理器
// ...
}
odin build .

依赖的负担

在一个项目中引入的每一个依赖,都是一种潜在的负担。在生活中,依赖意味着责任;如果被依赖的一方出了问题,承担责任的可能是依赖方。软件的包依赖也不例外。

这种负担主要体现在两个方面:

  1. 安全风险:依赖带来了严重的安全问题,尤其是当开发者盲目信任从互联网上随便找来的库时。

  2. Bug 负担:除了安全问题,依赖还意味着 Bug 的负担。比如,有一个团队在广泛使用的 SDL2 库中发现了“大量 Bug”,因此他们考虑从头重写窗口和输入处理系统。自己编写代码的好处在于:“这是我们的代码,当出现问题时,我们可以依赖它并修复它。” 作者并不是主张所有东西都要从零写起,而是承认:所有依赖都是一种负担和责任。


包管理器引发的问题

包管理器总体上被认为对编程生态是净负面的,应当尽可能避免。它们让开发者很容易把自己和他人拖入“地狱”。

一个重要问题出现在那些语言自身对“包”概念定义不清的场景下。此时,包管理器试图去定义“什么是包”,结果反而引发了各种混乱。

以 JavaScript 为例,它有多个包管理器(如 npm),由于每个包管理器对“包”的定义不同,最终导致出现了一个“包管理器的管理器”。这种怪物般的东西之所以会存在,正是因为不同包管理器彼此定义冲突。

此外,通用型的自动化包管理器会隐藏项目中的复杂性与问题,而这些复杂性和问题本该保持可见。


文化与社会层面的问题

包管理器的快速普及,与大量新入行的工程师几乎同时发生。他们一开始就接触这些工具,从而导致对包管理器的依赖与一系列深层的文化和社会问题紧密相关。

无凭无据的高信任

程序员往往不去审查自己的代码,更不会审查第三方依赖,而是默认从互联网上下载的随机代码是“可用的”。这被视为一种社会性问题:程序员大多来自高信任的发达国家,并将这种社会信任模式应用到了网络世界。高信任环境的结果就是:只要有一个人恶意破坏,或者在一个广泛依赖的代码里写下了一个“搞笑的 Bug”,就可能让数百万用户受到影响。

盖尔曼失忆效应(Gell-Mann Amnesia Effect)

该效应描述了这样一种情况:人们在阅读自己熟悉领域的文章时,能轻易识别错误或不实之处(例如关于骑马的文章);但在阅读自己不熟悉领域的文章时(例如 JavaScript),却会下意识地认为其内容完全正确,从而忘记了信息源整体上并不可靠。

在编程中,这种效应表现为:工程师一方面批评自己同事“写不出好代码”,另一方面却选择“无条件信任我下载的每一个开源包”。他们对所谓的“开源大神”怀有不切实际的过度信任,而这些人也许并不比他们批评的同事更有水平。

演化选择压力不足

编程行业非常年轻,最多也就 70–75 年的历史。这还不足以形成“良好的进化选择压力”或真正的智慧。与现代科学(已发展 500 年)或工程学不同,编程行业的演化速度并不足以淘汰掉糟糕的实践。

目前已知的少数规律,比如 康威定律(Conway’s Law)(代码的结构反映了开发它的公司的组织结构),之所以显眼,正是因为这个行业还缺乏足够多经过时间检验的智慧和法则。

缓解替代方案

如果目标是完全避免使用包管理器,推荐的做法是 手动管理依赖

这意味着需要 手动复制和 vendor 化 每一个依赖包,并固定其特定版本。相比那些隐藏复杂性的自动化系统,这种方式能确保稳定性、可靠性和可维护性。

Go 的悖论与缓解策略

某些语言,尤其是 Go,因为拥有内置的包管理机制并且具备一个非常优秀的核心/标准库(“电池齐全”),能够在更长时间内避免陷入依赖陷阱。这个强大的标准库使开发者能够完成很多任务,比如构建一个 Web 服务器,而无需依赖第三方库。Go 全面的标准库减少了对外部小工具包的依赖。

对于使用 Go Modules(go mod)等自动化工具的开发者,有一些缓解策略可以用来抵消自动化带来的风险:

  1. 优先使用标准库: 在寻找外部依赖之前,开发者应首先确认标准库(std library)中是否已经提供了解决方案。

  2. go get 视为架构决策: 引入新依赖时需要进行尽职调查,包括检查其代码质量、社区活跃度、issue 列表以及维护状况。

  3. 定期审计依赖树: 使用 go mod graphgo list -m all 等工具来检查项目实际依赖了什么,并清理掉不必要的依赖。

  4. 拥抱复制: 遵循 “少量的复制优于少量的依赖” 的原则,可以避免不必要的外部链接。

最终的结论是:最好的包管理器可能是使用得最少的那个

参考

  • Excerpt from the Odin FAQ on managing code without a package manager: https://odin-lang.org/docs/faq/#how-do-i-manage-my-code-without-a-package-manager
  • Tony Bai article on the topic: https://tonybai.com/2025/09/13/package-managers-are-evil

更多内容

最近文章:

随机文章:


更多该系列文章,参考medium链接:

https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a

English post: https://programmerscareer.com/pm-is-evil-25/
作者:微信公众号,Medium,LinkedIn,Twitter
发表日期:原文在 2025-09-21 15:12 时创作于 https://programmerscareer.com/zh-cn/pm-is-evil-25/
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

Golang 构造函数,函数选项和建造者模式 Go 错误处理的演化和最新提案

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×