项目结构、代码风格与标识符命名
5 使用得到公认且广泛使用的项目结构
Go 语言典型项目结构:
-
以构建二进制可执行文件为目的的 Go 项目结构
- cmd 或 app 目录:存放项目要构建的可执行文件对应的 main 包的源文件。main 包用于做命令行参数解析、资源初始化、日志设施初始化、数据库连接初始化等工作,之后就会将程序的执行权限交给更高级的执行控制对象。
- pkg 或 lib 目录:存放项目自身要使用并且同样也是可执行文件对应 main 包要依赖的库文件。该目录下的包可以被外部项目引用,算是项目导出包的一个聚合。
- Makefile:项目构建工具所用脚本的“代表”,它可以代表任何第三方构建工具所用的脚本。对于构建脚本较多的项目,也可以建立 build 目录,并将构建脚本的规则属性文件、子构建脚本放入其中。
- go.mod 和 go.sum:Go 语言包依赖管理使用的配置文件。
- vendor 目录(可选):用于在项目本地缓存特定版本依赖包的机制。
-
以只构建库为目的的 Go 项目结构
- 去除了 cmd 和 pkg 两个子目录:由于仅构建库,没必要保留存放二进制文件 main 包源文件的 cmd 目录;由于 Go 库项目的初衷一般都是对外部(开源或组织内部公开)暴露 API,因此也没必要将其单独聚合到 pkg 目录下面。
- vendor 不再是可选目录:推荐仅通过 go.mod 明确表述项目依赖的模块或包以及版本要求。
-
关于 internal 目录:无论是哪种类型的 Go 项目,对于不想暴露给外部引用,仅限项目内部使用的包,在项目结构上可以通过 internal 包机制来实现。
上述参考项目结构与产品设计开发领域的最小可行产品(Minimum Viable Product,MVP)的思路异曲同工,开发者可以在这样一个最小的项目结构核心的基础上根据实际需要进行扩展。
6 提交前使用 gofmt 格式化源码
gofmt 最大的特点是没有提供任何关于代码风格设置的命令行选项和参数。
使用 gofmt:
- gofmt -s:简化代码,没有副作用,默认选项。
- gofmt -r 'pattern -> replacement':代码“微重构”,表达式级别的替换。
- gofmt -l:按格式要求输出满足条件的文件列表。
使用 goimports:在 gofmt 的基础上增加了对包导入列表的维护功能,可根据源码的最新变动自动从导入包列表中增删包。
7 使用 Go 命名惯例对标识符进行命名
计算机科学中只有两件难事:缓存失效和命名。——Phil Karlton,Netscape 架构师
两个原则:
-
简单且一致:在某种情况下,Go 命名惯例选择了简洁命名 + 注释辅助解释的方式,而不是一个长长的名字。
- 包:建议以小写形式的单个单词命名;包名可以不唯一,对于包名冲突的情况,可以在导入包时使用一个显式包名来指代导入的包;包名应尽量与包导入路径的最后一个路径分段保持一致;兼顾包导出的标识符(如变量、常量、类型、函数等)的命名,在名字中不要再包含包名。
- 变量、类型、函数和方法:标识符命名采用驼峰命名法;如果缩略词的首字母是大写的,那么其他字母也要保持全部大写,如 HTTP、CBC 等;一般来说,Go 标识符仍以单个单词/字母作为命名首选(从 Go 标准库统计结果来看);保持变量声明与使用之间的距离越近越好,或者在第一次使用变量之前声明该变量;保持简短命名变量含义上的一致性,如 k(键值)、v(元素值)、i(下标)、t(时间/类型)、b(byte)。
- 常量:不要求全大写;数值型常量会在使用时根据左值类型和其他运算操作数的类型进行自动转换。
- 接口:优先以单个单词命名,对于拥有唯一方法或通过多个拥有唯一方法的接口组合而成的接口,Go 语言的惯例是用“方法名+er”命名,如 Writer、Reader、Closer、ReadWriteCloser;推荐尽量定义小接口,并通过接口组合的方式构建程序。
-
利用上下文辅助命名:让最短的名字携带足够多的信息,即在不影响可读性的前提下,兼顾一致性原则,尽可能地用短小的名字命名标识符。参看 2014 年 Andrew Gerrard 在一次关于 Go 命名演讲 中用到的代码。
// 坏的命名
func RuneCount(buffer []byte) int {
runeCount := 0
for index := 0; index < len(buffer); {
if buffer[index] < RuneSelf {
index++
} else {
_, size := DecodeRune(buffer[index:])
index += size
}
runeCount++
}
return runeCount
}
// 好的命名
func RuneCount(b []byte) int {
count := 0
for i := 0; i < len(b); {
if b[i] < RuneSelf {
i++
} else {
_, n := DecodeRune(b[i:])
i += n
}
count++
}
return count
}
关注我
参考
《Go 语言精进之路:从新手到高手的编程思想、方法和技巧》——白明