为什么 go.mod 中有两段 require 结构?

唐可
唐可
发布时间:2026-08-25| 133人浏览过
原创

前言

在 Go 项目里执行一次 go mod tidy 后,很多人都会注意到 go.mod 里出现了两段 require,看上去像重复配置。其实这正是 Go 用来区分直接依赖和间接依赖的整理方式。弄清它们各自代表什么、为什么会被拆开、又能不能手动分组,读 go.mod 时就不会再把正常结构误判成异常。

在使用 go mod 管理依赖时,go.mod 往往是排查版本问题、理解项目结构的第一入口。但不少人执行一次 go mod tidy 之后,会发现文件里突然出现两段 require,看起来像是被“拆开”了。其实这不是异常,而是 Go 对依赖关系做了更清晰的分层展示。看懂这两段的含义后,你就能更快判断哪些包是项目主动使用的,哪些只是被上游依赖带进来的。

为什么 go.mod 会出现两段 require

在 Go 项目中,go.mod 是 go mod 的核心配置文件。常见配置项通常包括 module、go 和 require。一个经过 go mod tidy 整理后的文件,常见结构类似下面这样:

module github.com/project/demo

go 1.24.0

require (
    github.com/gin-gonic/gin v1.10.1
    gorm.io/driver/sqlite v1.6.0
    gorm.io/gorm v1.30.1
)

require (
    github.com/bytedance/sonic v1.11.6 // indirect
    github.com/bytedance/sonic/loader v0.1.1 // indirect
    github.com/goccy/go-json v0.10.2 // indirect
    github.com/pelletier/go-toml/v2 v2.2.2 // indirect
    ... // 省略其他依赖
)

这两段 require 并不是重复声明,而是 Go 按依赖来源做的一次分类:前一段是项目直接使用的依赖,后一段是依赖链条中传递进来的间接依赖。这样做的好处是,开发者打开文件时能先看清项目真正“主动依赖”了什么,再去判断其余依赖是如何被带入的。

第一段:项目直接依赖

第一段 require 表示直接依赖,也就是项目源码里通过 import 明确引用到的模块。这部分通常最值得关注,因为它直接反映了项目选用了哪些框架、数据库驱动或工具库。

require (
    github.com/gin-gonic/gin v1.10.1
    gorm.io/driver/sqlite v1.6.0
    gorm.io/gorm v1.30.1
)

例如上面这段里,github.com/gin-gonic/gin、gorm.io/driver/sqlite 和 gorm.io/gorm 都属于业务代码直接使用到的模块。平时阅读一个项目的技术栈,通常先看这里就够了,因为这部分最能说明项目本身依赖了哪些能力。

第二段:带 // indirect 的间接依赖

第二段 require 列的是间接依赖,特征也很明显:每一项后面都带有 // indirect 标记。它的意思不是“没用”,而是“项目没有直接 import 这个模块,但某个直接依赖需要它”。

用两列卡片区分 go.mod 中直接依赖与间接依赖的专栏配图
两段 require 到底在区分什么两段 require 的核心区别,不在数量,而在依赖来源。
require (
    github.com/bytedance/sonic v1.11.6 // indirect
    github.com/bytedance/sonic/loader v0.1.1 // indirect
    github.com/goccy/go-json v0.10.2 // indirect
    github.com/pelletier/go-toml/v2 v2.2.2 // indirect
    ... // 省略其他依赖
)

这类依赖本质上是依赖树的一部分。你的代码没有直接引用它们,但项目要能稳定构建、测试和运行,仍然需要这些模块及其固定版本。项目规模越大,依赖链越深,第二段通常就越长。

因此,看到大量 // indirect 并不代表 go.mod 混乱,更多时候只是说明项目引入的上游库本身依赖比较丰富。它们被明确记录下来,反而更有利于追踪版本来源和排查兼容性问题。

Go 1.17 之后,为什么这种结构更常见

自 Go 1.17 之后,执行 go mod tidy 时,工具会更明确地区分直接依赖和间接依赖,并把项目涉及到的间接依赖也列出来。于是,两段式 require 就成了很多项目里的常见形态。

展示 Go 1.17 之后 go mod tidy 如何整理依赖并保留版本信息的暖色专栏配图
go mod tidy 为何会整理成两段Go 1.17 之后,go mod tidy 更倾向把依赖关系按角色写清楚。

这么做的价值在于版本可重复性。虽然它不等同于 npm 或 composer 中的 .lock 文件,但思路有相似之处:尽量把依赖关系和版本边界表达清楚,减少未来升级时出现“不知道为什么变了”的情况。对团队协作、CI 构建和长期维护来说,这一点很重要。

require 不一定只能分成两段

两段式是常见结果,但并不是唯一合法写法。go.mod 中的 require 可以按需要自行分组,因此你也可能看到三段式甚至更多分段。

有些项目会单独拉出一段,专门声明需要手动锁定版本的间接依赖。比如某个上游模块升级后带来了兼容问题,这时就可以显式指定某个间接依赖的版本,以确保构建结果稳定。

这种写法并不违背 Go 的依赖管理规则。即便是间接依赖,只要你在 go.mod 中显式写出,并带上 // indirect 标记,通常也不会被 go mod tidy 轻易清掉。换句话说,go mod 在默认自动整理之外,也保留了足够的人工控制空间。

实际阅读 go.mod 时该怎么看

如果你的目标是快速理解项目依赖,最直接的办法是先看第一段 require,因为那里列出的就是项目真正直接使用的模块。只有在排查构建异常、版本冲突或某个包为何被引入时,再去看第二段以及对应的 // indirect 项。

从这个角度看,go.mod 里出现两段 require 不是多余设计,而是为了把“主动依赖”和“传递依赖”分开显示。它让文件更可读,也让版本控制更稳定。随着 Go 生态不断成熟,go mod 的这套机制已经越来越接近“简单但够用,而且遇到复杂场景也能手动干预”的状态。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。