转自:Go项目目录该怎么组织?官方终于出指南了! https://mp.weixin.qq.com/s/zbV2UwHYvgsuwnXR2Vr04Q
长久以来,在Go语言进阶的学习和实践之路上,Go项目目录究竟如何布局一直是困扰大家的一个问题,这是因为Go官方针对这个问题迟迟没有给出说法,更没有提供标准供大家参考。仅有Go语言项目技术负责人Russ Cox在一个开源项目的issue中给出了他[关于Go项目结构的最小标准布局的想法[1]](#参考资料)。
近期Go官方文档集合中新增了一篇名为[“Organizing a Go module”的文档[6]](#参考资料),细读之后,我发现这不就是大家期待已久的Go项目目录布局的官方指南吗!
在这篇文章中,我们就来看看这份[官方指南[7]](#参考资料),看看官方推荐的Go项目目录布局是什么样子的。
1.Go项目的类型
我们知道Go项目(project)一般有两类:library和executable。library是以构建库为目的的Go项目,而executable则是以构建二进制可执行文件为目的的Go项目。
“Organizing a Go module”这篇文档也是按照Go项目类型为Gopher提供项目布局建议的。这篇文档将library类的项目叫作package类,executable类的项目叫作command。下面的示意图展示了“Organizing
a Go module”这篇文档的说明顺序:
从图中看到,“Organizing a Go module”这篇文档总共给出7种项目的布局建议。接下来,我们就来逐一看一下。
2. 官方版Go项目目录布局指南
2.1 basic package
我们先从package类开始。最简单的package类的Go项目是basic package,下面就是一个basic package类的项目目录布局的示例:
project-root-directory/
├── go.mod
├── modname.go
└── modname_test.go
或
project-root-directory/
├── go.mod
├── modname.go
├── modname_test.go
├── auth.go
├── auth_test.go
├── hash.go
└── hash_test.go
我们看到basic
package类项目非常简单,repo下面只有一个导出package,这个package包含一个或多个包源文件。以repo托管在github上为例,如果这个repo的url为github.com/someuser/modname,那么该repo下的module
root和导出package的导入路径通常与repo url一致,都为github.com/someuser/modname。
你的代码要依赖该module,直接通过下面import语句便可以将该module导入:
import "github.com/someuser/modname"
2.2 basic command
和basic package一样,basic command类项目是以构建可执行二进制程序为目的的Go项目中最简单的一类。下面是basic command类项目的一个示例:
project-root-directory/
├── go.mod
└── main.go
或
project-root-directory/
├── go.mod
├── main.go
├── auth.go
├── auth_test.go
├── hash.go
└── hash_test.go
从示例我们可以看到,basic command类项目的repo下面只可构建出一个可执行文件,main函数放在main.go中,其他源文件也在repo根目录下,并同样放在main包中。
还是以repo托管在github上为例,如果这个repo的url为github.com/someuser/modname,那么我们可以通过下面命令安装这个command的可执行程序:
go install github.com/someuser/modname@latest
2.3 package with supporting packages
稍复杂或规模稍大的一些package类项目,会将很多功能分拆到supporting packages中,并且通常项目作者是不希望导出这些supporting
packages的,这样这些supporting packages便可以不作为暴露的API的一部分,后续重构和优化起来十分方便,对package的用户也是无感的。这样Go官方建议将这些supporting
packages放入[internal目录[8]](#参考资料)。
注:internal目录是[Go 1.4版本[9]](#参考资料)引入的机制,简单来说放在internal中的包是local的,不能导出到module之外,但module下的某些内部代码可以导入internal下的包。如今一般都会将internal放在项目的根目录下,所以项目下的所有代码都可以导入internal下的包。
下面是一个带有supporting packages的package类项目的目录布局示例:
project-root-directory/
├── go.mod
├── modname.go
├── modname_test.go
└── internal/
├── auth/
│ ├── auth.go
│ └── auth_test.go
└── hash/
├── hash.go
└── hash_test.go
modname.go或modname_test.go可以通过下面导入语句使用internal下面的包:
import "github.com/someuser/modname/internal/auth"
### 2.4 command with supporting packages
有了package with supporting packages的说明后,再来看command with supporting packages就更简单了,下面是一个示例:
project-root-directory/
├── go.mod
├── main.go
└── internal/
├── auth/
│ ├── auth.go
│ └── auth_test.go
└── hash/
├── hash.go
└── hash_test.go
和package with supporting packages不同的是,main.go使用的包名为main,这样Go编译器才能将其构建为command。
2.5 multiple packages
作为一个库项目,作者可能要暴露不止一个package,可能是多个packages。这不会给Go项目目录布局带来过多复杂性,我们只需多建立几个导出package的目录就ok了。下面是一个multiple
packages的示例:
project-root-directory/
├── go.mod
├── modname.go
├── modname_test.go
├── auth/
│ ├── auth.go
│ ├── auth_test.go
│ └── token/
│ ├── token.go
│ └── token_test.go
├── hash/
│ ├── hash.go
│ └── hash_test.go
└── internal/
└── trace/
├── trace.go
└── trace_test.go
我们看到这个示例在repo(以托管在github.com/user/modname下为例)顶层放置了多个导出包:
github.com/user/modname
github.com/user/modname/auth
github.com/user/modname/hash
并且顶层的auth目录下还有一个二级的导出包token,其导入路径为:
github.com/user/modname/auth/token
所有这些导出包的supporting
packages还是按惯例放在了internal目录下,比如:github.com/user/modname/internal/trace,这些包是local的,不能被该module之外的代码所依赖。