Go 注释的魅力
目录
作者:isasiyu
链接:https://juejin.cn/post/6943411995412693000
注释是记录和交流代码信息的有价值的工具。它们是几乎所有编程语言的共同特性,Go 也不例外。然而,Go 程序中的注释所能做的远不止为代码的读者提供信息。在这篇文章中,我将重点介绍一些鲜为人知的注释用法,这些注释具有特殊的(几乎是神奇的)行为。
Go 注释语法
继承于 C 语言的语法,Go 支持熟悉的单行和多行注释,使用 // 和 /*...*/ 具体如下:
// 行尾都是注释
// 换行需要另一个`//`标记
var foo int; // 注释可以从任何地方开始
/* 结束标记之前的所有内容都是注释
This includes new lines
包括新的一行 */
/* 多行注释也可以是单行 */
即使对 Go 有一定编程经验的人也会从他们读过的代码甚至自己写过的代码中识别出这种注释语法。然而,虽然注释的内容被 Go 编译器忽略了,但这并不意味着 Go 工具集完全忽略了它。本文的提示将显示一组特殊格式的注释,以及它们在 Go 中的用法。
godoc 文件文本
在 Go 中最常见的“神奇”注释形式可能是对 Go 的内置文档工具 godoc 的注释。godoc 的工作原理是扫描包中的所有 .go 文件(忽略任何 _test.go 声明前的注释(无任何中间代码或空行)。然后,godoc 将使用注释文本来形成包的文档。
例如,为了记录一个函数,我们只需将一行或多行注释放在其声明的前一行:
// Foo 将 foo 给定的字符串,如果字符串不能被 foo 赋值
// 返回一个 error
func Foo(s string) error {
...
}
在任何导出和包级别的类型、函数、方法、变量或常量声明之前,可以使用相同的注释样式:
package objects
// 对象是通用的
type Object struct {}
// Bar 是 bar 的对象,如果不是则返回 error
func (o Object) Bar() error {
return nil
}
// List 包含所有当前注册的 Object
var List []Object
// MaxCount 确定允许的最大对象数
const MaxCount = 50
因为只处理导出的包级注释,所以开发人员可以自由地在方法/函数体中使用注释,而不用担心注释被无意中添加到公共文档中。
godoc 还提供了一种生成包级文档的方法,通过解析在包声明之前发现的任何注释:
// 包对象完成基本对象的叙述
package objects
应该注意的是,godoc 在生成包索引时使用包文档注释的第一句话(例如,请参见golang.org/pkg/)所以一定要写…
如您所见,注释提供了一种非常简单的方法来提供开发人员和用户文档,而无需复杂的语法或其他文档文件。
构建约束
注释在 Go 中的第二个特殊用法是构建约束。
作为一种编程语言,Go 的一个关键特性是它支持多种操作系统和体系结构。通常相同的代码可以用于多个平台,但在某些情况下,操作系统或体系结构特定的代码只应用于某些目标。标准的 go-build 工具可以通过理解以操作系统名称和/或体系结构结尾的程序应该只用于与这些标记匹配的目标来处理一些这样的情况。例如,一个名为 foo_linux.go 的文件,将仅为 Linux 操作系统编译,foo_amd64.go 用于 amd64 体系结构,foo_windows_amd64.go 用于在 amd64 体系结构上运行的 64 位 windows 系统。
但是,这些命名约定在更复杂的情况下会失败,例如当同一代码可以用于多个(但不是所有)操作系统时。对于这些情况,Go有构建约束的概念——由Go build读取的巧尽心思构建的注释,以确定编译Go程序时要引用哪些文件。 生成约束是遵循以下规则的注释:
- 以前缀
+build开头,后跟一个或多个空格 - 位于包声明之前的文件顶部
- 在它和包声明之间至少有一个空行,以防止它被视为包文档
而不是把文件命名为 foo_linux.go 。我们可以把下面的注释放在文件 foo.go 的开头:
// +build linux
package main
...
然而,当引用多个体系结构和/或操作系统时,构建约束的威力就会出现。使用以下规则组合生成约束:
- 开始构建标签!都被否定了
- 以空格分隔的生成标记在逻辑上是 或(OR)
- 用逗号分隔的构建标记在逻辑上是 和(AND)
- 多行上的构建约束在逻辑上是 和(AND)
根据上述规则,以下约束将文件限制为 Linux 或 Darwin(MacOS):
// +build linux darwin
package main
...
当约束同时需要 Windows 和 i386:
// +build windows,386
package main
...
上述约束也可以写在两行上,如下所示:
// +build windows
// +build 386
package main
...
除了指定操作系统和体系结构外,构建约束还可以通过 ignore 标记完全忽略文件(尽管任何与有效体系结构或操作系统不匹配的文本都可以工作):
// +build ignore
package main
...
应该注意的是,这些构建约束(以及前面提到的命名约定)也适用于测试文件,因此可以以类似的方式执行特定于体系结构/操作系统的测试。
构建约束的全部功能在这里的 go-build 文档中有详细说明。
生成代码
在 Go 中注释的另一个有趣的替代用法是通过 Go generate 命令生成代码。go generate 是标准 go 工具箱的一部分,通过运行用户指定的外部命令以编程方式生成源(或其他)文件。通过扫描 .go 程序生成包含要运行的命令的巧尽心思构建的注释,然后执行它们。
具体来说,go generate 查找以 go:generate 开头的注释(注释和文本之间没有空格),如下所示:
//go:generate <command> <arguments>
...
与构建约束不同,go:generate 注释可以位于 .go 源文件中的任何位置(尽管典型的 go 习惯用法是将它们放置在文件的开头附近)。
go generate 的一个常见用途是通过这里提供的 stringer 工具提供可读的数字常量表示。stringer 文档提供了以下示例,解释了它的操作。给定自定义类型 Pill ,带枚举常量:
type Pill int
const (
Placebo Pill = iota
Aspirin
Ibuprofen
Paracetamol
Acetaminophen = Paracetamol
)
运行 stringer -type Pill 命令将创建一个新的源文件 pill_string.go ,提供以下方法:
func (p Pill) String() string
例如,它允许打印常量的名称,例如:
fmt.Printf("pill type: %s", pill)
但是需要记住包中每个适用类型的正确命令和参数可能很复杂,因此我们可以将以下注释添加到包中的 .go 文件中:
//go:generate stringer -type=Pill
...
然后运行 go generate 将使用正确的参数触发 stringer 调用,从而生成 Pill 字符串方法。考虑到这一点,我们可以看到 stringer 和 go generate 对程序员有很大的好处,特别是在一个包中有多个自定义类型的情况下。
Cgo
我将讨论的 Go 中注释的最后一个特殊用法是 C 集成工具 Cgo 。Cgo 允许 Go 程序直接调用 C 代码,允许在 Go 中重用已建立的 C 库。要在 Go 程序中使用 Cgo ,首先要导入伪包 “C” 。一旦导入,Go 程序就可以引用 C.size_t 之类的原生 C 类型和 C.putchar() 之类的函数。
然而,C 语言编程的某些方面并不容易翻译。为了处理这些问题,Cgo 特别使用 import "C" 语句前面的注释( Cgo 术语中称为序言)作为提供各种 C 特定配置项的方法。
其中一项是 #include 指令。几乎每个 C 程序都需要 #include 指令来指示头文件的位置。Go 语言没有任何本地等效命令( import 在包上工作,而不是头文件),因此 Cgo 解析来自序言的 #include 语句。例如:
// #include <stdio.h>
// #include <errno.h>
import "C"
序言部分的评论不仅限于 #include 语句。事实上,import 语句之前的任何注释都将被视为标准 C 代码,然后 Go 可以通过 C 包引用它们。例如,在序言中:
// #include <stdio.h>
//
// static void myprint(char *s) {
// printf("%s\n", s)
// }
import "C"
然后,我们可以在 Go 中引用这个新定义的 C 函数,如下所示:
C.myprint(C.String("foo"))
最后,为了处理编译器和类似的选项,Cgo引入了 #Cgo 指令,该指令可用于设置环境变量、编译器标志和运行 pkg-config 命令,如下所示:
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #cgo pkg-config: png cairo
// #include <png.h>
import "C"
所有这些序言定义都有助于使使用 Cgo 的程序与 go 构建工具无缝集成(几乎)而不是需要额外的创建文件或其他脚本的复杂性。
结论
我写这篇文章是为了向新的 Go 程序员介绍一些不太知名的注释使用。我希望你觉得它有趣有用。有关这些概念的更多详细信息,请访问以下链接: