05|标准先行:Go项目的布局标准是什么?

你好,我是Tony Bai。

在前面的讲解中,我们编写的Go程序都是简单程序,一般由一个或几个Go源码文件组成,而且所有源码文件都在同一个目录中。但是生产环境中运行的实用程序可不会这么简单,通常它们都有着复杂的项目结构布局。弄清楚一个实用Go项目的项目布局标准是Go开发者走向编写复杂Go程序的第一步,也是必经的一步。

但Go官方到目前为止也没有给出一个关于Go项目布局标准的正式定义。那在这样的情况下,Go社区是否有我们可以遵循的参考布局,或者事实标准呢?我可以肯定的告诉你:有的。在这一节课里,我就来告诉你Go社区广泛采用的Go项目布局是什么样子的。

要想了解Go项目的结构布局以及演化历史,全世界第一个Go语言项目是一个最好的切入点。所以,我们就先来看一下Go语言“创世项目”的结构布局是什么样的。

Go语言“创世项目”结构是怎样的?

什么是“Go语言的创世项目”呢?其实就是Go语言项目自身,它是全世界第一个Go语言项目。但这么说也不够精确,因为Go语言项目从项目伊始就混杂着多种语言,而且以C和Go代码为主,Go语言的早期版本C代码的比例还不小。

我们先用 loccount工具对Go语言发布的第一个 Go 1.0版本分析看看:

$loccount .
all          SLOC=460992  (100.00%)	LLOC=193045  in 2746 files
Go           SLOC=256321  (55.60%)	LLOC=109763  in 1983 files
C            SLOC=148001  (32.10%)	LLOC=73458   in 368 files
HTML         SLOC=25080   (5.44%)	LLOC=0       in 57 files
asm          SLOC=10109   (2.19%)	LLOC=0       in 133 files
... ...

你会发现,在1.0版本中,Go代码行数占据一半以上比例,但是C语言代码行数也占据了32.10%的份额。而且在后续Go版本演进过程中,Go语言代码行数占比还在逐步提升,直到Go 1.5版本实现自举后,Go语言代码行数占比将近90%,C语言比例下降为不到1%,这一比例一直延续至今。

虽然C代码比例下降,Go代码比例上升,但Go语言项目的布局结构却整体保留了下来,十多年间虽然也有一些小范围变动,但整体没有本质变化。作为Go语言的“创世项目”,它的结构布局对后续Go社区的项目具有重要的参考价值,尤其是Go项目早期src目录下面的结构。

为了方便查看,我们首先下载Go语言创世项目源码:

$git clone https://github.com/golang/go.git

进入Go语言项目根目录后,我们使用tree命令来查看一下Go语言项目自身的最初源码结构布局,以Go 1.3版本为例,结果是这样的:

$cd go // 进入Go语言项目根目录
$git checkout go1.3 // 切换到go 1.3版本
$tree -LF 1 ./src // 查看src目录下的结构布局
./src
├── all.bash*
├── clean.bash*
├── cmd/
├── make.bash*
├── Make.dist
├── pkg/
├── race.bash*
├── run.bash*
... ...
└── sudo.bash*

从上面的结果来看,src目录下面的结构有这三个特点。

首先,你可以看到,以all.bash为代表的代码构建的脚本源文件放在了src下面的顶层目录下。

第二,src下的二级目录cmd下面存放着Go相关可执行文件的相关目录,我们可以深入查看一下cmd目录下的结构:

$ tree -LF 1 ./cmd
./cmd
... ...
├── 6a/
├── 6c/
├── 6g/
... ...
├── cc/
├── cgo/
├── dist/
├── fix/
├── gc/
├── go/
├── gofmt/
├── ld/
├── nm/
├── objdump/
├── pack/
└── yacc/

我们可以看到,这里的每个子目录都是一个Go工具链命令或子命令对应的可执行文件。其中,6a、6c、6g等是早期Go版本针对特定平台的汇编器、编译器等的特殊命名方式。

第三个特点,你会看到src下的二级目录pkg下面存放着运行时实现、标准库包实现,这些包既可以被上面cmd下各程序所导入,也可以被Go语言项目之外的Go程序依赖并导入。下面是我们通过tree命令查看pkg下面结构的输出结果:

# tree -LF 1 ./pkg
./pkg
... ...
├── flag/
├── fmt/
├── go/
├── hash/
├── html/
├── image/
├── index/
├── io/
... ...
├── net/
├── os/
├── path/
├── reflect/
├── regexp/
├── runtime/
├── sort/
├── strconv/
├── strings/
├── sync/
├── syscall/
├── testing/
├── text/
├── time/
├── unicode/
└── unsafe/

虽然Go语言的创世项目的src目录下的布局结构,离现在已经比较久远了,但是这样的布局特点依然对后续很多Go项目的布局产生了比较大的影响,尤其是那些Go语言早期采纳者建立的Go项目。比如,Go调试器项目Delve、开启云原生时代的Go项目Docker,以及云原生时代的“操作系统”项目Kubernetes等,它们的项目布局,至今都还保持着与Go创世项目早期相同的风格。

当然了,这些早期的布局结构一直在不断地演化,简单来说可以归纳为下面三个比较重要的演进。

演进一:Go 1.4版本删除pkg这一中间层目录并引入internal目录

出于简化源码树层次的原因,Go语言项目的Go 1.4版本对它原来的src目录下的布局做了两处调整。第一处是删除了Go源码树中“src/pkg/xxx”中pkg这一层级目录而直接使用src/xxx。这样一来,Go语言项目的源码树深度减少一层,更便于Go开发者阅读和探索Go项目源码。

另外一处就是Go 1.4引入internal包机制,增加了internal目录。这个internal机制其实是所有Go项目都可以用的,Go语言项目自身也是自Go 1.4版本起,就使用internal机制了。根据internal机制的定义,一个Go项目里的internal目录下的Go包,只可以被本项目内部的包导入。项目外部是无法导入这个internal目录下面的包的。可以说,internal目录的引入,让一个Go项目中Go包的分类与用途变得更加清晰。

演进二:Go1.6版本增加vendor目录

第二次的演进,其实是为了解决Go包依赖版本管理的问题,Go核心团队在Go 1.5版本中做了第一次改进。增加了vendor构建机制,也就是Go源码的编译可以不在GOPATH环境变量下面搜索依赖包的路径,而在vendor目录下查找对应的依赖包。

Go语言项目自身也在Go 1.6版本中增加了vendor目录以支持vendor构建,但vendor目录并没有实质性缓存任何第三方包。直到Go 1.7版本,Go才真正在vendor下缓存了其依赖的外部包。这些依赖包主要是golang.org/x下面的包,这些包同样是由Go核心团队维护的,并且其更新速度不受Go版本发布周期的影响。

vendor机制与目录的引入,让Go项目第一次具有了可重现构建(Reproducible Build)的能力。

演进三:Go 1.13版本引入go.mod和go.sum

第三次演进,还是为了解决Go包依赖版本管理的问题。在Go 1.11版本中,Go核心团队做出了第二次改进尝试:引入了Go Module构建机制,也就是在项目引入go.mod以及在go.mod中明确项目所依赖的第三方包和版本,项目的构建就将摆脱GOPATH的束缚,实现精准的可重现构建。

Go语言项目自身在Go 1.13版本引入go.mod和go.sum以支持Go Module构建机制,下面是Go 1.13版本的go.mod文件内容:

module std

go 1.13

require (
	golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8
	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语言项目自身所依赖的包在go.mod中都有对应的信息,而原本这些依赖包是缓存在vendor目录下的。

总的来说,这三次演进主要体现在简化结构布局,以及优化包依赖管理方面,起到了改善Go开发体验的作用。可以说,Go创世项目的源码布局以及演化对Go社区项目的布局具有重要的启发意义,以至于在多年的Go社区实践后,Go社区逐渐形成了公认的Go项目的典型结构布局。

现在的Go项目的典型结构布局是怎样的?

一个Go项目通常分为可执行程序项目和库项目,现在我们就来分析一下这两类Go项目的典型结构布局分别是怎样的。

首先我们先来看Go可执行程序项目的典型结构布局。

可执行程序项目是以构建可执行程序为目的的项目,Go社区针对这类Go项目所形成的典型结构布局是这样的:

$tree -F exe-layout 
exe-layout
├── cmd/
│   ├── app1/
│   │   └── main.go
│   └── app2/
│       └── main.go
├── go.mod
├── go.sum
├── internal/
│   ├── pkga/
│   │   └── pkg_a.go
│   └── pkgb/
│       └── pkg_b.go
├── pkg1/
│   └── pkg1.go
├── pkg2/
│   └── pkg2.go
└── vendor/

这样的一个Go项目典型布局就是“脱胎”于Go创世项目的最新结构布局,我现在跟你解释一下这里面的几个要点。

我们从上往下按顺序来,先来看 cmd目录。cmd目录就是存放项目要编译构建的可执行文件对应的main包的源文件。如果你的项目中有多个可执行文件需要构建,每个可执行文件的main包单独放在一个子目录中,比如图中的app1、app2,cmd目录下的各app的main包将整个项目的依赖连接在一起。

而且通常来说,main包应该很简洁。我们在main包中会做一些命令行参数解析、资源初始化、日志设施初始化、数据库连接初始化等工作,之后就会将程序的执行权限交给更高级的执行控制对象。另外,也有一些Go项目将cmd这个名字改为app或其他名字,但它的功能其实并没有变。

接着我们来看 pkgN目录,这是一个存放项目自身要使用、同样也是可执行文件对应main包所要依赖的库文件,同时这些目录下的包还可以被外部项目引用。

然后是 go.modgo.sum ,它们是Go语言包依赖管理使用的配置文件。我们前面说过,Go 1.11版本引入了Go Module构建机制,这里我建议你所有新项目都基于Go Module来进行包依赖管理,因为这是目前Go官方推荐的标准构建模式。

对于还没有使用Go Module进行包依赖管理的遗留项目,比如之前采用dep、glide等作为包依赖管理工具的,建议尽快迁移到Go Module模式。Go命令支持直接将dep的Gopkg.toml/Gopkg.lock或glide的glide.yaml/glide.lock转换为go.mod。

最后我们再来看看 vendor目录。vendor是Go 1.5版本引入的用于在项目本地缓存特定版本依赖包的机制,在Go Modules机制引入前,基于vendor可以实现可重现构建,保证基于同一源码构建出的可执行程序是等价的。

不过呢,我们这里将vendor目录视为一个可选目录。原因在于,Go Module本身就支持可再现构建,而无需使用vendor。 当然Go Module机制也保留了vendor目录(通过go mod vendor可以生成vendor下的依赖包,通过go build -mod=vendor可以实现基于vendor的构建)。一般我们仅保留项目根目录下的vendor目录,否则会造成不必要的依赖选择的复杂性。

当然了,有些开发者喜欢借助一些第三方的构建工具辅助构建,比如:make、bazel等。你可以将这类外部辅助构建工具涉及的诸多脚本文件(比如Makefile)放置在项目的顶层目录下,就像Go创世项目中的all.bash那样。

另外,这里只要说明一下的是,Go 1.11引入的module是一组同属于一个版本管理单元的包的集合。并且Go支持在一个项目/仓库中存在多个module,但这种管理方式可能要比一定比例的代码重复引入更多的复杂性。 因此,如果项目结构中存在版本管理的“分歧”,比如:app1和app2的发布版本并不总是同步的,那么我建议你将项目拆分为多个项目(仓库),每个项目单独作为一个module进行单独的版本管理和演进。

当然如果你非要在一个代码仓库中存放多个module,那么新版Go命令也提供了很好的支持。比如下面代码仓库multi-modules下面有三个module:mainmodule、module1和module2:

$tree multi-modules
multi-modules
├── go.mod // mainmodule
├── module1
│   └── go.mod // module1
└── module2
    └── go.mod // module2

我们可以通过git tag名字来区分不同module的版本。其中vX.Y.Z形式的tag名字用于代码仓库下的mainmodule;而module1/vX.Y.Z形式的tag名字用于指示module1的版本;同理,module2/vX.Y.Z形式的tag名字用于指示module2版本。

如果Go可执行程序项目有一个且只有一个可执行程序要构建,那就比较好办了,我们可以将上面项目布局进行简化:

$tree -F -L 1 single-exe-layout
single-exe-layout
├── go.mod
├── internal/
├── main.go
├── pkg1/
├── pkg2/
└── vendor/

你可以看到,我们删除了cmd目录,将唯一的可执行程序的main包就放置在项目根目录下,而其他布局元素的功用不变。

好了到这里,我们已经了解了Go可执行程序项目的典型布局,现在我们再来看看Go库项目的典型结构布局是怎样的。

Go库项目仅对外暴露Go包,这类项目的典型布局形式是这样的:

$tree -F lib-layout 
lib-layout
├── go.mod
├── internal/
│   ├── pkga/
│   │   └── pkg_a.go
│   └── pkgb/
│       └── pkg_b.go
├── pkg1/
│   └── pkg1.go
└── pkg2/
    └── pkg2.go

我们看到,库类型项目相比于Go可执行程序项目的布局要简单一些。因为这类项目不需要构建可执行程序,所以去除了cmd目录。

而且,在这里,vendor也不再是可选目录了。对于库类型项目而言,我们并不推荐在项目中放置vendor目录去缓存库自身的第三方依赖,库项目仅通过go.mod文件明确表述出该项目依赖的module或包以及版本要求就可以了。

Go库项目的初衷是为了对外部(开源或组织内部公开)暴露API,对于仅限项目内部使用而不想暴露到外部的包,可以放在项目顶层的internal目录下面。当然internal也可以有多个并存在于项目结构中的任一目录层级中,关键是项目结构设计人员要明确各级internal包的应用层次和范围。

对于有一个且仅有一个包的Go库项目来说,我们也可以将上面的布局做进一步简化,简化的布局如下所示:

$tree -L 1 -F single-pkg-lib-layout
single-pkg-lib-layout
├── feature1.go
├── feature2.go
├── go.mod
└── internal/

简化后,我们将这唯一包的所有源文件放置在项目的顶层目录下(比如上面的feature1.go和feature2.go),其他布局元素位置和功用不变。

好了,现在我们已经了解完目前Go项目的典型结构布局了。不过呢,除了这些之外,还要注意一下早期Go可执行程序项目的经典布局,这个又有所不同。

注意早期Go可执行程序项目的典型布局

很多早期接纳Go语言的开发者所建立的Go可执行程序项目,深受Go创世项目1.4版本之前的布局影响,这些项目将所有可暴露到外面的Go包聚合在pkg目录下,就像前面Go 1.3版本中的布局那样,它们的典型布局结构是这样的:

$tree -L 3 -F early-project-layout
early-project-layout
└── exe-layout/
    ├── cmd/
    │   ├── app1/
    │   └── app2/
    ├── go.mod
    ├── internal/
    │   ├── pkga/
    │   └── pkgb/
    ├── pkg/
    │   ├── pkg1/
    │   └── pkg2/
    └── vendor/

我们看到,原本放在项目顶层目录下的pkg1和pkg2公共包被统一聚合到pkg目录下了。而且,这种早期Go可执行程序项目的典型布局在Go社区内部也不乏受众,很多新建的Go项目依然采用这样的项目布局。

所以,当你看到这样的布局也不要奇怪,并且在我的讲解后,你应该就明确在这样的布局下pkg目录所起到的“聚类”的作用了。不过,在这里还是建议你在创建新的Go项目时,优先采用前面的标准项目布局。

小结

到这里,我们今天这门课就结束了。在这一节课里,我们学习了Go创世项目,也就是Go语言项目自身的项目源码布局,以及演进情况。在Go创世项目的启发下,Go社区在多年实践中形成了典型的Go项目结构布局形式。

我们将Go项目分为可执行程序项目和Go库项目两类进行了详细的项目典型布局讲解,这里简单回顾一下。

首先,对于以生产可执行程序为目的的Go项目,它的典型项目结构分为五部分:

  • 放在项目顶层的Go Module相关文件,包括go.mod和go.sum;
  • cmd目录:存放项目要编译构建的可执行文件所对应的main包的源码文件;
  • 项目包目录:每个项目下的非main包都“平铺”在项目的根目录下,每个目录对应一个Go包;
  • internal目录:存放仅项目内部引用的Go包,这些包无法被项目之外引用;
  • vendor目录:这是一个可选目录,为了兼容Go 1.5引入的vendor构建模式而存在的。这个目录下的内容均由Go命令自动维护,不需要开发者手工干预。

第二,对于以生产可复用库为目的的Go项目,它的典型结构则要简单许多,我们可以直接理解为在Go可执行程序项目的基础上去掉cmd目录和vendor目录。

最后,早期接纳Go语言的开发者所建立的项目的布局深受Go创世项目1.4版本之前布局的影响,将可导出的公共包放入单独的pkg目录下,我们了解这种情况即可。对于新建Go项目,我依旧建议你采用前面介绍的标准布局形式。

现在,如果你要再面对一个要用于生产环境的Go应用项目的布局问题,是不是胸有成竹了呢?

思考题

如果非要你考虑Go项目结构的最小标准布局,那么你觉得这个布局中都应该包含哪些东西呢?欢迎在留言区留下你的答案。

感谢你和我一起学习,也欢迎你把这节课分享给更多对Go项目布局感兴趣的朋友。我是Tony Bai,我们下节课见。

精选留言

  • Nlife

    2021-10-22 15:38:19

    老师,这句话"一个 Go 项目里的 internal 目录下的 Go 包,只可以被本项目内部的包导入。项目外部是无法导入这个 internal 目录下面的包的。" 能否再讲解具体一些呢?比如后续我们的课程中是否会讲到这块的实践操作?
    作者回复

    举个例子,假设我们有两个go module,两个module的结构如下:

    .
    ├── module1
    │   ├── go.mod
    │   ├── internal
    │   │   └── pkga
    │   ├── pkg1
    │   └── pkg2
    └── module2
    ├── go.mod
    └── pkg1

    module1中的internal/pkga包可以被module1的pkg1和pkg2包所导入。

    但无法被module2的pkg1包所导入。

    2021-10-26 16:02:02

  • mazhen

    2021-10-22 19:21:54

    这节课的内容非常实用,介绍了最新的最佳实践,大部go语言的书籍都缺少这部分内容。
  • Geek_c1467d

    2021-10-22 16:11:20

    另外goalng标准布局可以参考下这个:https://github.com/golang-standards/project-layout
    作者回复

    这个已经被Go官方否了,https://github.com/golang-standards/project-layout/issues/117#issuecomment-828503689。

    2021-10-26 15:43:38

  • Linuaa

    2021-10-23 21:32:00

    老师可以讲讲 ”Reproducible Build“ 吗,看了一些文章感觉抓不到重点。谢谢老师~
    作者回复

    可重现构建,顾名思义,就是针对同一份go module的源码进行构建,不同人,在不同机器(同一架构,比如都是x86-64),相同os上,在不同时间点都能得到相同的二进制文件。

    2021-10-26 16:36:48

  • 郭纯

    2021-10-22 15:36:17

    对于最小的布局 我觉的只要这几个文件就好了 main.go. go.mod go.sum. 既然是小项目代码量不多所有代码在 main.go 文件就好。
    作者回复

    Go语言技术负责人Russ Cox曾谈过这个问题,他认为一个项目的最小布局至少有一个go.mod,一个LICENSE(针对开源项目)。然后就像你说的,在项目根目录下放置go代码即可。对于tiny项目,一个main.go也是可以的。

    2021-10-26 15:36:14

  • 光明

    2022-02-05 13:47:51

    这一节虽然没有搞懂太多,反复看了3遍,后发现这一章节,是现行很多 GO 语言书籍中缺少部分。非常感谢Tony 老师的这么细致有详细的讲解。细微之处见真功夫。
    作者回复

    👍

    2022-02-06 12:15:38

  • alexgreenbar

    2022-04-21 19:38:44

    这些难道不是一门语言一开始就应该解决的问题吗?10多年过去了,go居然还在纠结这个,在这点上,感觉go的创建者们故意忽视了软件工业过去20年的积累,不比较语言本身,只考虑构建:java有maven,rust有cargo,并且它们都有集中可访问的repository用于分享,go到现在都没有这个机制,也是服了。
    作者回复

    我觉得你提到的是两件事:

    1. go项目标准布局的事儿

    到目前为止,Go官方并没有给出书面标准。文中内容也是基于Go项目自身以及Go社区的主流实践整理而得的。

    Go语言技术负责人Russ Cox曾谈过这个问题,但他仅给出对于go项目最小布局的观点,他认为一个项目的最小布局至少有一个go.mod,一个LIC
    ENSE(针对开源项目)。其他都有程序员自行确定。不可否认,没有基本标准布局,这的确给规模稍大一些的项目的开发人员带来困惑。

    2. 没有统一的集中的module/包库

    Go没有,且也是故意这么设计的。你提到Go团队故意忽视了软件工业过去20年的积累,但从Go团队角度来看,这是他们的一种解决安全风险的方案。可以看看这篇文章:https://tonybai.com/2022/04/02/how-go-mitigates-supply-chain-attacks

    从今年来npm暴露出的一系列安全问题来看,集中库的确也存在各种各样的问题。

    2022-04-22 13:10:52

  • Long_hz

    2021-10-22 09:23:50

    老师你好,请问一下loccount 工具编译的时候缺少go.mod需要怎么解决?
    作者回复

    loccount只是一个代码统计工具,你可以用其他类似的工具替代。如果非要编译loccount工具,并且它没有go.mod的话,可以下载loccount工具源码后,在你的本地为其创建一个go.mod,然后编译试试。

    2021-10-26 11:13:06

  • lesserror

    2021-10-22 17:45:39

    Tony Bail 老师的这一讲关于Go项目的布局标准的讲解非常专业。极客时间孔令飞老师的专栏,对这一布局方式很很好的实践。

    有以下疑问,烦请老师抽空解答一下:

    1. “ 这些依赖包是缓存在 vendor 目录下的”。那我可以是否可以理解为,接是把这些包的源码文件下载到本地的vendor目录中呢?

    2. “库项目仅通过 go.mod 文件明确表述出该项目依赖的 module 或包以及版本要求就可以了。” 请问一下,go.mod文件中还能表述依赖的 module吗? 我看go.mod文件中的内容一般不都是依赖的第三方包和版本吗?

    3. 使用vendor的优势是什么?对比使用 go module形式,只是访问第三方包的源码路径的不同吗?

    4. 老师,后面的项目代码会在这一讲的目录基础上来构建吗?这一讲没有实际的代码操作,如果没有实际的操作感受,很容易遗忘这些概念。
    作者回复

    感谢认真的思考和棒棒的问题,我也认真回答一下:)

    1. 是的,如果采用vendor模式,依赖包会缓存在vendor目录下。
    2. 在go module机制进入go之前,也就是gopath构建模式时代,我们谈到的所有依赖都是包与包的版本;但go module引入后,所有的版本信息都绑定在module上,所以你在go.mod中看到的require块中的依赖都是module与module的版本,不再是包。
    3. 06和07讲会提到。
    4. 06,07讲会有例子。

    2021-10-26 16:56:15

  • qinsi

    2021-10-22 16:49:14

    诶,ESR也写go了?
    作者回复

    是的。loccount就是它的作品。他还用go编写了将gcc代码从svn仓库无损(提交历史)地迁移到git的工具。可以看看他切换到go的感悟:https://gitlab.com/esr/reposurgeon/blob/master/GoNotes.adoc

    2021-10-26 15:42:41

  • 陈星宇(2.11)

    2022-07-25 13:52:04

    老师,没开发基础的听着有点吃力呢。
    作者回复

    这也是正常现象,再简单的编程语言对于没有开发基础的同学来说也是有不小门槛的:)。

    既然没有基础,那么别人看一遍,你就要看3-5遍。

    别人可能不亲手敲例子代码,你就必须要亲手敲例子代码。

    别人要问1个问题,你就要问出5个具体问题。

    同时,遇到不明白的开发概念(可能是语言无关)的时候,也要并行学习一些语言无关的开发相关基础知识。

    2022-07-25 22:02:56

  • 喜乐雅歌

    2022-02-06 12:59:31

    老师您好,我是一名初学者目前学了一些go语言基本的语法,有没有练习的习题可以练习,请老师帮助推荐一些练习的习题。
    作者回复

    《The Go Programming Language》这本书每节后面的习题可以做做。

    2022-02-08 11:34:54

  • 向阳花开

    2021-10-27 13:56:33

    老师老师加把劲,一周七天不断更😄
    作者回复

    哈哈,昼夜努力中。

    2021-10-28 05:35:59

  • 酥宝话不多

    2021-10-22 10:04:25

    内建函数 make 是第三方构建工具吗 ?
    作者回复

    make是unix/linux/mac上最常见的第三方构建管理辅助工具。

    2021-10-26 11:15:09

  • 运维夜谈

    2021-12-12 13:31:11

    老师,请教一下,“可再现重建”是什么意思?
    作者回复

    看一下 《Go语言第一课FAQ》吧 https://tonybai.com/go-course-faq ,那里有我的解释。

    2021-12-21 09:41:43

  • Geek_5d8f2f

    2021-11-16 15:54:08

    这节课看的云里雾里,哈哈哈。继续学习
    作者回复

    多看几遍,有问题就问。

    2021-11-22 12:41:44

  • 天意

    2021-11-05 14:18:48

    project
    ├── bin/
    │ ├── bee
    │ └── one
    ├── pkg/
    │ ├── sumdb/
    │ └── mod/
    │ └── github.com/
    │ └── cache/
    ├── src/
    │ │── one/
    │ │ └── main.go
    │ │ └── go.mod
    │ │
    │ └── two/
    │ └── main.go
    │ └── go.mod


    这种算是什么结构,可取么
    作者回复

    看了一下,感觉这个目录就是gopath下的目录,bin、src、pkg与gopath下的目录结构是一样的。

    另外目前看不出 像one,two这样的go module是否是在单独的git repo中管理。

    有了go module构建模式后,go项目不必放在gopath目录下面,但如果像上面例子中放在gopath目录下面也是ok的。

    05讲其实针对的是每个module下面的布局。对应的是例子里的one、two这样的module。例子中的one、two 两个module都是最简形式,和我们讲解的内容不冲突。

    2021-11-09 10:32:34

  • 罗杰

    2021-10-22 09:38:38

    对于刚用 Go 开发的时候,凑合能用就行,那个时候一心想着是功能开发,等到功能完成之后,目录结构优化过两三版。感谢老师详细的讲解,对于这种目录结构困惑,我觉得最快找答案的方式就是看优秀的开源库。
    作者回复

    历史悠久的优秀开源库,它的布局也在演化。不过有些也没变。

    2021-10-26 11:14:25

  • rocking

    2022-09-05 15:46:00

    老师,对于我一个新手,一开始就讲包的依赖,是很懵的,是不是调一调顺序,是不是对于新手来说 先了解了解go的语法,函数,demo搞起来之后,再讲这些依赖的关系,我自己也是java开发,刚开始学习也没直接学依赖 都是循序渐进,谢谢!
    作者回复

    众口难调啊(手动允悲),还有很多小伙伴说要先讲go module依赖管理,他们认为:这是go的难点,如果早搞明白,后面都不是问题,并且再复杂的go项目也能搞懂结构,便于更深入的动手实践。

    其实吧,这个专栏也不用完全按顺序学,可以根据自己的情况,在适合的章节跳过,然后需要的时候再回来复习一下:)。

    2022-09-06 18:58:50

  • Demon.Lee

    2022-06-15 11:10:28

    查了一下相关单元测试代码的布局,我看像 go 官方源码,kubernetes 源码等,都有独立的 test 目录,下面再放各种子目录。
    但 go 官方源码中也有很多单元测试代码直接跟源码放一起,比如 errors.go 和 errors_test.go 在一个目录下(且 package 还不同)。

    请教老师:
    1)单元测试的代码是直接跟源码放一起,还是单独放在一个 test 目录下面,我觉得应该放在 test 目录下聚合比较好,当然,如果就一两个文件,放在根目录下与 main.go 同级应该也没问题。
    2)但从另一个角度来分析,一般情况下一个目录下是不能有两个 package 的,但 go 官方显然为了单元测试开了小灶,说明很重视,所以跟源码放一起更好?

    想了解一下行业内的最佳实践是什么,谢谢您。
    作者回复

    好问题,观察的也很细心。

    不过,Go官方的建议从来都是“包的单元测试与包放在一起的”。

    你提到的go源码中的test目录,你可以打开那个目录下的README看一下其说明,就会理解了。下面是摘录:

    The test directory contains tests of the Go tool chain and runtime.
    It includes black box tests, regression tests, and error output tests.
    They are run as part of all.bash.

    To run just these tests, execute:

    go run run.go

    Standard library tests should be written as regular Go tests in the appropriate package.

    The tool chain and runtime also have regular Go tests in their packages.

    这里着重看:
    The main reasons to add a new test to this directory are:

    * it is most naturally expressed using the test runner; or
    * it is also applicable to `gccgo` and other Go tool chains.
    ~

    另外你提到的包内的test源文件的包可以是其他包名(xxx_test),这是因为xx_test.go文件本就不会和当前目录内的包一并参与go build,xxx_test.go是go test的输入。

    2022-06-15 14:03:12