Go语言项目标准结构应该如何组织的?

每当我们写一个非hello world实用程序的Go程序或库时,我们都会在项目结构、代码风格和标识符命名这三个“门槛”面前折腾很久,甚至得不到满意的答案。

在本文中,我们将详细介绍如何跨过Go项目结构的“门槛”,帮助您更快地进入Go语言的核心腹地,提高学习效率。

除非是像hello world这样简单的程序,否则每当我们写实用程序或库时,都会遇到使用什么项目结构的问题(通常一个Go项目对应一个repository)。在Go中,项目结构也很重要,因为它决定了项目内部包的布局和包依赖关系,也影响了外部项目对项目内包的依赖和引用。

Go项目本身的目录结构

我们来看看Go语言本身的项目结构,世界上第一个Go项目。

Go项目的项目结构从1.0版本发布开始就非常稳定,Go项目的顶层结构至今基本没有变化。截至go project commit 1e3ffb0c (2019.5.14),go项目结构如下。

$ tree -LF 1 ~/go/src/github.com/golang/go
./go
├── api/
├── AUTHORS
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── doc/
├── favicon.ico
├── lib/
├── LICENSE
├── misc/
├── PATENTS
├── README.md
├── robots.txt
├── src/
└── test/

作为Go的“创始项目”,其项目结构布局对后续其他Go项目具有重要参考意义,尤其是早期Go项目中src目录下的结构,被Go社区广泛用作模板Go 应用程序项目结构。我们以早期Go 1.3版本的src目录下的结构为例。

$ tree -LF 1 ./src
./src
├── all.bash*
├── all.bat
├── all.rc*
├── clean.bash*
├── clean.bat
├── clean.rc*
├── cmd/
├── lib9/
├── libbio/
├── liblink/
├── make.bash*
├── make.bat
├── Make.dist
├── make.rc*
├── nacltest.bash*
├── pkg/
├── race.bash*
├── race.bat
├── run.bash*
├── run.bat
├── run.rc*
└── sudo.bash*

关于上面src目录下面的结构,我总结了三个特点。

  1. 代码构建的脚本源文件放在src下的顶层目录下。

  2. src 下的二级目录 cmd 存放了 go 工具链相关的可执行文件(如:go、gofmt 等)及其主要包源文件的主目录。

$ tree -LF 1 ./cmd
./cmd
...
├── 6a/
├── 6c/
├── 6g/
...
├── cc/
├── cgo/
├── dist/
├── fix/
├── gc/
├── go/
├── gofmt/
├── ld/
├── nm/
├── objdump/
├── pack/
└── yacc/
  1. src下的二级目录,pkg,存放着上面cmd下各个工具链所依赖的packages、go runtime、go standard library的源文件。
$ tree -LF 1 ./pkg
./pkg
...
├── flag/
├── fmt/
├── go/
├── io/
├── log/
├── math/
...
├── syscall/
├── testing/
├── text/
├── time/
├── unicode/
└── unsafe/

从 Go 1.3 版本至今,Go 项目下的 src 目录发生了一些结构上的变化。

  • Go 1.4 从 Go 源代码树中的 src/pkg/xxx 中删除了 pkg 级别,而是直接使用 src/xxx。
  • Go 1.4 在 src 下增加了 internal 目录,用来存放不能外部导入的,只供 Go 项目使用的包。
  • Go 1.6 在 src 下增加了 vendor 目录,但是 Go 项目本身真正启用了 Go 1.7 中的 vendor 机制。vendor目录下存放着Go项目自身对外部项目的依赖,主要是golang.org/x下的包,包括:net、text、crypto等。该目录下的包随着每个Go版本的发布而更新。
  • Go 1.13版本在src下增加了go.mod和go.num,实现了go项目本身go module的迁移。go项目中的所有包都放在了std模块下,其依赖仍然是golang.org/x下的各种包
// Go 1.13版本go项目src下面的go.mod
module std

go 1.12

require (
    golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68
    golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
    golang.org/x/sys v0.0.0-20190529130038-5219a1e1c5f8 // indirect
    golang.org/x/text v0.3.2 // indirect
)

这是最新 Go 1.16 版本的 src 目录中的完整布局。

├── Make.dist
├── README.vendor
├── all.bash*
├── all.bat
├── all.rc*
├── bootstrap.bash*
├── buildall.bash*
├── clean.bash*
├── clean.bat
├── clean.rc*
├── cmd/
├── cmp.bash
├── go.mod
├── go.sum
├── internal/
├── make.bash*
├── make.bat
├── make.rc*
├── race.bash*
├── race.bat
├── run.bash*
├── run.bat
├── run.rc*
├── testdata/
...
└── vendor/

Go语言项目典型目录结构

GO语言项目最小标准目录结构

一个Go应用项目结构的标准布局是什么样子的,Go官方团队从来没有给出过一个参考标准。不过,作为Go语言项目的技术负责人, Russ Cox在开源项目的一期中给出了他对Go项目结构最小标准布局的思考。他认为Go项目的最低标准布局应该如下。

// 在Go项目仓库根路径下

- go.mod
- LICENSE
- xx.go
- yy.go
...

or

- go.mod
- LICENSE
- package1
  - package1.go
- package2
  - package2.go
...

至于 pkg、cmd、docs,这些目录不应该是 Go 项目的标准结构的一部分,或者至少不需要。相信 Russ Cox 给出的最小标准布局与 Go 的“简单”哲学是一致的,足够灵活,可以满足各种 Go 项目的需求。

然而,在 Russ Cox 详细阐述最低标准之前,Go 社区处于“非标准”状态,早期 Go 语言自身的项目结构对现有大量 Go 开源项目的影响依然持久。对于更大的 Go 应用程序,我们势必会扩展“最小标准布局”。这个扩展显然不会盲目,但还是会参考Go语言项目自身的结构布局,所以我们有以下非官方标准建议的结构布局。

可执行的Go语言项目目录结构

基于Go语言项目本身的早期结构及其后续演变,Go社区经过多年的Go语言实践积累,逐渐形成了符合Russ Cox最小标准布局的典型项目结构,如下图所示。
在这里插入图片描述
以上是一个典型的支持构建二进制可执行文件的Go项目结构(在cmd下),我们分别看一下各个重要目录的用途。

  • cmd目录:存放项目要编译构建的可执行文件对应的主包源文件。如果有多个可执行文件要构建,则每个可执行文件的主包放在单独的子目录下,如图中的app1和app2;cmd目录下每个app的主包,将整个项目的依赖连接在一起;而且一般来说,主包应该是非常简洁的。我们会在main包中做一些命令行参数解析、资源初始化、日志工具初始化、数据库连接初始化等,之后我们会将程序的执行权交给更高级的执行控制对象;也有一些go项目把cmd的名字改成了app,但是它的实用性并没有改变。

  • pkg目录:项目本身要用到的,也是主包对应的可执行文件依赖的库文件;同时,该目录下的包也可以被外部项目引用,作为项​​目导出包的“聚合”;也有一些项目会 pkg 名称为 lib,但目录用途保持不变;因为Go语言项目在1.4版本去掉了pkg目录,所以有一些项目直接把包放在项目的根路径下,但是我觉得对于一些比较大的项目,包太多会成为顶层目录该项目不够简洁和“拥挤”。对于复杂的 Go 项目,我建议保留 pkg 目录。

  • Makefile:这里的Makefile是项目构建工具使用的脚本的“代表”,它可以代表任何第三方构建工具使用的脚本。对于大型项目来说似乎是必不可少的。在一个典型的Go项目中,项目构建工具的脚本通常放在项目的顶层目录下,比如这里的Makefile;对于构建脚本较多的项目,也可以创建构建目录,将构建脚本的规则属性文件和子构建脚本放入其中。

  • go.mod和go.sum:用于Go语言包依赖管理的配置文件。Go 1.11 引入了 go modules,go module 在 Go 1.16 成为了默认的依赖包管理和构建机制。所以对于新的 Go 项目,我们推荐使用 go modules 来进行包依赖管理。对于没有使用go modules进行包管理的项目(可能主要是使用go 1.11以前版本的go项目),可以换成dep的Gopkg.toml和Gopkg.lock或者glide的glide.yaml和glide.lock等.

  • vendor 目录(可选):vendor 是 Go 1.5 引入的机制,用于在项目本地缓存版本特定的依赖包,在引入 go modules 机制之前,可以实现基于 vendor 的可重现构建,以确保从相同构建的可执行文件源代码是等价的。go modules 本身可以在没有供应商的情况下实现可重现的构建。modules 本身可以在不需要 vendor 的情况下实现可重现的构建,但是 go modules 机制还保留了 vendor 目录(go mod vendor 在 vendor 下生成依赖项;go build -mod=vendor 启用基于 vendor 的构建),所以这里的 vendor 目录作为可选目录。一般我们只将vendor目录保留在项目的根目录下,否则会造成依赖选择上不必要的复杂性。

Go 1.11 引入了一个模块作为属于同一版本控制单元的包的集合。虽然 Go 支持项目/存储库中的多个模块,但这种管理方法可能会带来比一定比例的代码重复更多的复杂性。因此,如果项目结构中存在版本控制“分歧”,例如 app1 和 app2 版本并不总是同步,那么我建议将项目拆分为多个项目(存储库),每个项目都是一个单独的模块,用于单独的版本控制和演进.

库的Go语言项目目录结构

Go 1.4 发布时,Go 语言项目本身就去掉了 src 下的 pkg 层。这种结构变化对仅为库目的而构建的 Go 库类型项目的结构有一些影响。我们来看一个典型的 Go 库类型项目的结构,如下图所示。
在这里插入图片描述
我们看到库类型的项目结构也兼容 Go 项目的最低标准布局,但比旨在构建二进制可执行文件的 Go 项目更简单。

  • 删除了cmd和pkg子目录:由于该库只是一个组件库,因此无需保留存放二进制文件主要包源文件的cmd目录;由于 Go 库项目的初衷是将 API 暴露给外界(开源或内部组织),因此没有必要将其聚合在 pkg 目录下。
  • vendor不再可选:对于库类型的项目,我们不建议在项目中放置vendor目录来缓存库自身的第三方依赖;库项目应该只通过 go.mod(或其他包依赖管理工具的清单文件)明确说明项目依赖的模块或包以及版本要求。

关于internal目录

对于上述任何类型的Go项目,对于不想暴露给外部引用,而只供内部使用的包,可以通过Go 1.4引入的内部包机制来实现项目结构。对于库项目,最简单的方法是在顶层添加一个内部目录,将所有不想暴露给外部的包都放在该目录下,例如项目中的 ilib1 和 ilib2下面的结构。

// 带internal的Go库项目结构
$tree -F ./chapter2/sources/GoLibProj
GoLibProj
├── LICENSE
├── Makefile
├── README.md
├── go.mod
├── internal/
│   ├── ilib1/
│   └── ilib2/
├── lib.go
├── lib1/
│   └── lib1.go
└── lib2/
    └── lib2.go

这样,根据go内部机制的原理,内部目录的ilib1和ilib2可以被其他目录(如lib.go、lib1/lib1.go等)的代码导入使用,GoLibProj目录为根目录,但不是通过 GoLibProj 目录之外的代码。这允许选择性地公开 API 包。当然 internal 可以放在项目结构中的任何目录级别,但是项目结构设计者清楚什么要暴露给外部代码以及什么只能在兄弟目录或子目录中使用是至关重要的。

对于旨在构建二进制可执行类型的项目,我们还可以将不想暴露给外部的包聚合到项目顶层路径的internal下,呼应暴露给包的聚合目录pkg外部。

总结

这篇文章详细介绍了Go语言项目结构布局的历史和Go项目结构的事实标准。本文中构建二进制可执行文件类型和库类型的两个项目参考结构经过多年的实践被Go社区认可并广泛使用,并且兼容Russ Cox提出的Go项目最小标准布局,具有参考价值对于稍大的 Go 项目。然而,它们不是必需的,在 Go 语言的早期,将所有源文件放在位于项目根目录的根包中的做法在一些小型项目中同样有效。

对于旨在构建二进制可执行类型的项目,受 Go 1.4 项目结构的影响,删除 pkg 层次结构也是许多项目选择的结构布局。

上述参考项目结构类似于产品设计和开发领域的“最小可行产品”(mvp)的思想,开发者可以根据自己的实际需要在这样一个最小“项目结构核心”的基础上进行扩展。

在这里插入图片描述
By 极客Furzoom

参考文章

go project layout

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
GoHub 基于 Github Pages 提供在线 Golang 文档阅读.。特征:便捷的文档项目组织Go Doc API 翻译双语对照阅读渲染 Markdown 或更多编程语言文档对 GoHub 有任何建议或问题, 请至 GoHub Wiki 和 Issues实现基础得益于 GitHub API 和 RawGit, GoHub 项目代码和 Golang 翻译文档项目代码是分离的。GoHub 通过 GitHub API 获取项目的 Latest release Tag,从 RawGit CDN 获取该 Tag 下的文档。RawGit CDN 中的数据是静态的, 不提供更新服务。更新 Latest release Tag 可使 GoHub 展现新的文档. 对于没有建立 Latest release Tag 的项目, 您可以 fork 后, 在 fork 项目中建立 Latest release Tag。组织方式GoHub 使用文件 golist.json 来组织文档项目. 为简化逻辑使用两种结构.Object方式 表示包文档索引: 全部以 Object 组织, list 为子包列表.{     "std": {         "type": "doc_zh_CN.go",         "repo": "golang-china/golangdoc.translations/src",         "list": {             "archive/tar": "tar包实现了tar格式压缩文件的存取.",             "archive/zip": "zip包提供了zip档案文件的读写服务."         }     } }数组方式 表示项目索引: 每个条目都是 GitHub 上得一个项目, repo 下必须含有 golist.json 文件. 该文件内容可以为包文档索引或者包文档索引.[     {         "repo": "golang-china/golangdoc.translations",         "description": "Go std 文档"     },     {         "repo": "gohub/google",         "description":"Google Go 文档"     } ]GoHub 的 golist.json 为文档源头, repo 所有者通过定义 golist.json 自由拓扑文档关系.Go 标准GoLang 标准库的翻译文档来自 Golang-China 的 golangdoc.translations 项目。期待您参与该项目并改善翻译文档.致谢GoHub 的设计灵感来自 FlatDoc。Powered by:jQuery  New Wave JavaScriptmarked  a markdown parserbase64.js Base64 implementation for JavaScripthighlight.js Syntax highlighting for the WebJingYes CSS3 Framework 标签:GoHub

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枫竹梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值