2. 依赖管理工具:Python的pip与Poetry、Node.js的npm与yarn、Java的Maven与Gradle对比

依赖管理,说白了就是管好你项目里的「第三方库」。

我见过太多团队,因为依赖版本不一致,导致「我机器上能跑,你机器上就崩」的惨案。嗯,这节我们就来聊聊主流的依赖管理工具,看看它们各自的门道。

2.1 Python:pip 与 Poetry

Python 的依赖管理,曾经是个老大难问题。pip 虽然能用,但说实话,它太「原始」了。

2.1.1 pip + requirements.txt

这是最传统的方式。你运行 pip freeze > requirements.txt,把当前环境的包列表导出来。别人拿到这个文件,再 pip install -r requirements.txt 安装。

听起来很简单对吧?但坑不少。

我曾经踩过的坑: requirements.txt 只记录了「当前安装的版本」,不记录「依赖的依赖」。比如你装了 A 包,A 依赖 B 包 1.0 版本。但某天 B 升级到 2.0,你重新部署时,可能就装上了不兼容的版本。

另外,pip 没有「锁文件」的概念。你没法保证两次安装的结果完全一致。这在生产环境里,简直是定时炸弹。

2.1.2 Poetry:现代 Python 依赖管理

Poetry 的出现,算是把 Python 带入了现代依赖管理时代。我个人习惯用 Poetry 来管理新项目。

它有几个核心文件:

  • pyproject.toml:声明式配置,写清楚你直接依赖了哪些包
  • poetry.lock:锁文件,锁定所有依赖(包括间接依赖)的精确版本

举个例子,初始化项目:

poetry new my-project
cd my-project
poetry add requests

这会自动生成 pyproject.toml 和 poetry.lock。别人克隆项目后,运行 poetry install,就能得到完全一致的依赖环境。

我的建议: 团队协作时,一定要把 poetry.lock 提交到 Git 仓库。这样大家的环境才能保持一致。

2.2 Node.js:npm 与 yarn

Node.js 的依赖管理,故事就更多了。npm 是官方工具,yarn 是 Facebook 后来推出的替代品。

2.2.1 npm:老牌工具

npm 使用 package.json 记录依赖,package-lock.json 作为锁文件。基本用法:

npm init -y
npm install express

npm 5 之前没有锁文件,那会儿真是「安装靠运气」。后来加了 package-lock.json,情况好多了。

但 npm 有个问题:它的依赖树是嵌套的。你装一个包,它可能把它的依赖装到 node_modules 的子目录里。这会导致同一个包被安装多次,浪费磁盘空间。

2.2.2 yarn:更快的选择

yarn 的出现,就是为了解决 npm 的痛点。它使用 yarn.lock 作为锁文件,安装速度更快,而且有离线缓存功能。

用法也很简单:

yarn init -y
yarn add express

yarn 最大的改进是「确定性安装」。同样的 yarn.lock,在任何机器上安装结果都一样。这一点,npm 后来也学去了。

核心区别:

  • npm 用 package-lock.json,yarn 用 yarn.lock
  • yarn 安装速度通常更快
  • yarn 有离线缓存,断网也能装
  • npm 现在也支持 npm ci 命令,实现类似 yarn 的确定性安装

2.3 Java:Maven 与 Gradle

Java 世界的依赖管理,又是另一番景象。Maven 是元老,Gradle 是后起之秀。

2.3.1 Maven:约定优于配置

Maven 使用 XML 文件 pom.xml 管理依赖。它的核心理念是「约定优于配置」——你只要按它的目录结构放代码,它就能自动编译、测试、打包。

一个典型的 pom.xml 片段:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.0</version>
    </dependency>
</dependencies>

Maven 没有锁文件,但它有「依赖仲裁」机制。如果同一个包出现多个版本,Maven 会按「最短路径优先」原则选择一个。这有时候会出问题——你明明想用 2.0 版本,结果被一个间接依赖拉到了 1.0。

我曾经遇到的情况: 项目里引入了两个不同版本的日志框架,结果运行时日志死活打不出来。排查了半天,才发现是 Maven 的依赖仲裁「帮」我选了个旧版本。

2.3.2 Gradle:灵活高效

Gradle 使用 Groovy 或 Kotlin DSL 来写构建脚本,比 Maven 的 XML 灵活得多。它也有锁文件的概念,叫 gradle.lockfile。

Gradle 的依赖声明:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.7.0'
    testImplementation 'junit:junit:4.13.2'
}

Gradle 最大的优势是「增量构建」——只重新编译有变化的部分。大型项目里,这能省下大量时间。

我的建议: 新项目用 Gradle,老项目用 Maven。Gradle 的学习曲线稍微陡一点,但长期来看,它的灵活性和性能都更好。

2.4 工具对比总结

说了这么多,我们来个直观的对比:

特性 pip Poetry npm yarn Maven Gradle
锁文件 无 poetry.lock package-lock.json yarn.lock 无 gradle.lockfile
配置文件 requirements.txt pyproject.toml package.json package.json pom.xml build.gradle
依赖仲裁 无 自动解决 自动解决 自动解决 最短路径优先 最新版本优先
离线缓存 无 有 有 有 有 有
构建速度 慢 中等 中等 快 慢 快

2.5 我的选择建议

你想想看,选工具其实就是在选「团队协作的规范」。我个人习惯这样选:

  • Python 项目: 新项目用 Poetry,老项目用 pip + requirements.txt + pip-tools
  • Node.js 项目: 团队小用 npm,团队大用 yarn(确定性安装更可靠)
  • Java 项目: 新项目用 Gradle,老项目用 Maven(别折腾迁移,不值得)

核心原则: 不管用哪个工具,一定要把锁文件提交到 Git 仓库。这是保证「我机器上能跑,你机器上也能跑」的最简单方法。

嗯,这节就到这里。下一节我们会深入聊聊「语义化版本号」——为什么 2.1.0 和 2.1.1 可能不兼容?为什么 3.0.0 升级要谨慎?到时候见。