Go 程序的基础
让我们深入探讨 Go 程序的一个基础。在本课中,你将学习如何组织代码的目录结构,这有助于分离关注点、促进理解,并支持代码的演进。
目录结构的重要性
命令行程序可以很小,也可以非常庞大。大型程序包含大量代码,并由多位不同的开发者在长时间内开发。合理的目录结构对程序的长期成功有很大帮助。良好的目录结构在多个方面都具有优势。
代码组织
每个大型程序的代码都应以一种易于定位、搜索和理解的方式进行组织。为特定角色设立独立的目录是一种经过时间检验的策略。
开发者协作
当程序的特定方面位于不同的目录中时,开发者之间的协作会更加顺畅。许多类型的更改只涉及特定目录,而跨功能的更改则可能涉及多个目录。当执行相似功能的文件位于同一目录中时,审查更改会更加方便。开发者也更容易发现遗漏或不符合一致模式的更改。
程序演进
随着程序的增长,目录和子目录有助于扩展程序,而不会使由目录表示的整体结构变得更加复杂。一个包含十个文件的目录与包含一百个文件的目录相比,复杂度并没有显著增加。
Go 工作区、$GOPATH 和模块
在 Go 1.11 之前,Go 程序必须遵循一种严格的层级结构,称为工作区(workspace)。$GOPATH 环境变量包含一个或多个目录,其下有三个子目录:
- src
- bin
- pkg
你的所有源代码以及依赖项的源代码通常会放在 src 下,按源代码控制系统(如 Git)的来源进一步组织。编译后的包放在 pkg 目录下,编译后的可执行文件放在 bin 目录下。
Go 1.11 引入了初步的模块支持,通过一个特殊的环境变量,允许代码放在任何位置,只要包含 go.mod 文件即可。Go 1.12 默认支持模块,但不允许在 $GOPATH 内部使用。Go 1.13 支持模块在任何位置使用,包括在 $GOPATH 内部。
有关 Go 模块的更多信息,请参阅 https://github.com/golang/go/wiki/Modules。
Go 程序的组成部分
有许多方式可以划分和分类进入 Go 程序的文件。我将重点介绍一些最常见的类型。
包(Packages)
Go 代码是按包组织的。一个包是一个包含文件和子包(或子目录)的目录。以下是 repo_manager 包:
1$ tree repo_manager/ 2repo_manager/ 3├── repo_manager.go 4├── repo_manager_suite_test.go 5└── repo_manager_test.go
repo_manager 位于 pkg/repo_manager,包含三个 Go 文件。
包内的代码可以访问该包中定义的所有变量和函数。而其他包中的代码只能访问名称首字母大写的公共变量和函数。每个源代码文件顶部都必须有一个与包名匹配的 package 声明。
1package repo_manager 2 3...
命令(Commands)
命令是可执行文件。在命令行程序中,程序本身就是一个命令。一个命令会包含一个带有 main() 函数的文件。如果项目有多个可执行文件,则会有多个命令(可能位于多个子目录中),每个都包含 main() 函数。每个命令都需要单独构建。它们都可以导入相同的包,但不建议导入其他命令。如果多个命令需要共享的功能,应将其放入一个两个命令都能导入的包中。
测试(Tests)
测试对于任何程序都至关重要,命令行程序也不例外。Go 认识到这一点,并通过标准库的 testing 包和 go test 命令原生支持创建和运行测试。如果文件名以 _test.go 结尾,Go 会将其视为测试文件,并在输入 go test 时运行其中的测试。我们将在课程后面详细讨论测试。
例如,repo_manager 包包含一个名为 repo_manager_test.go 的测试文件。
文档(Documentation)
程序通常包含代码注释之外的文档。特别是命令行程序通常会对每个命令和选项提供详细的程序内帮助信息。然而,有些程序也有外部文档。
元数据和构建信息
这里通常会找到各种文件,如 README、LICENSE、Dockerfile、go.mod 等。这些文件不是 Go 源文件,但以各种方式支持程序。特别是 go.mod 文件描述了程序的依赖关系。