Go 文档注释
目录
“文档注释”是指紧接在顶层包、常量、函数、类型和变量声明之前出现且其间没有换行的注释。每个导出的(首字母大写的)名称都应该有文档注释。
go/doc 和 go/doc/comment 包提供了从 Go 源代码中提取文档的能力,各种工具都利用了这一功能。go
doc
命令查找并打印给定包或符号的文档注释。(符号是顶层常量、函数、类型或变量。)网络服务器 pkg.go.dev 显示公共 Go 包的文档(在其许可证允许使用时)。提供该站点的程序是 golang.org/x/pkgsite/cmd/pkgsite,它也可以在本地运行以查看私有模块的文档或在没有互联网连接的情况下查看文档。语言服务器 gopls 在 IDE 中编辑 Go 源文件时提供文档。
本页其余部分介绍了如何编写 Go 文档注释。
包
每个包都应该有一个介绍该包的包注释。它提供了与整个包相关的信息,并通常设定了对包的预期。特别是在大型包中,包注释可以简要概述 API 中最重要的部分,并在需要时链接到其他文档注释,这会很有帮助。
如果包很简单,包注释可以很简短。例如
// Package path implements utility routines for manipulating slash-separated
// paths.
//
// The path package should only be used for paths separated by forward
// slashes, such as the paths in URLs. This package does not deal with
// Windows paths with drive letters or backslashes; to manipulate
// operating system paths, use the [path/filepath] package.
package path
[path/filepath]
中的方括号创建了一个文档链接。
从这个例子可以看出,Go 文档注释使用完整的句子。对于包注释,这意味着第一句话以“Package
对于多文件包,包注释应该只存在于一个源文件中。如果多个文件有包注释,它们将被连接起来形成整个包的一个大注释。
命令
命令的包注释类似,但它描述的是程序的行为,而不是包中的 Go 符号。第一句话习惯上以程序本身的名称开头,首字母大写,因为它位于句子的开头。例如,这里是 gofmt 包注释的删节版本
/*
Gofmt formats Go programs.
It uses tabs for indentation and blanks for alignment.
Alignment assumes that an editor is using a fixed-width font.
Without an explicit path, it processes the standard input. Given a file,
it operates on that file; given a directory, it operates on all .go files in
that directory, recursively. (Files starting with a period are ignored.)
By default, gofmt prints the reformatted sources to standard output.
Usage:
gofmt [flags] [path ...]
The flags are:
-d
Do not print reformatted sources to standard output.
If a file's formatting is different than gofmt's, print diffs
to standard output.
-w
Do not print reformatted sources to standard output.
If a file's formatting is different from gofmt's, overwrite it
with gofmt's version. If an error occurred during overwriting,
the original file is restored from an automatic backup.
When gofmt reads from standard input, it accepts either a full Go program
or a program fragment. A program fragment must be a syntactically
valid declaration list, statement list, or expression. When formatting
such a fragment, gofmt preserves leading indentation as well as leading
and trailing spaces, so that individual sections of a Go program can be
formatted by piping them through gofmt.
*/
package main
注释的开头使用了语义换行,其中每个新句子或长短语都单独占一行,这使得代码和注释演变时更容易阅读差异。后面的段落碰巧没有遵循这个约定,而是手动换行。无论哪种方式对您的代码库最好都可以。无论哪种方式,go
doc
和 pkgsite
在打印时都会重新换行文档注释文本。例如
$ go doc gofmt
Gofmt formats Go programs. It uses tabs for indentation and blanks for
alignment. Alignment assumes that an editor is using a fixed-width font.
Without an explicit path, it processes the standard input. Given a file, it
operates on that file; given a directory, it operates on all .go files in that
directory, recursively. (Files starting with a period are ignored.) By default,
gofmt prints the reformatted sources to standard output.
Usage:
gofmt [flags] [path ...]
The flags are:
-d
Do not print reformatted sources to standard output.
If a file's formatting is different than gofmt's, print diffs
to standard output.
...
缩进的行被视为预格式化文本:它们不会被重新换行,并在 HTML 和 Markdown 展示中以代码字体打印。(下面的语法部分提供了详细信息。)
类型
类型的文档注释应该解释该类型的每个实例代表或提供什么。如果 API 很简单,文档注释可以相当简短。例如
package zip
// A Reader serves content from a ZIP archive.
type Reader struct {
...
}
默认情况下,程序员应该期望一个类型仅在同一时间被单个 goroutine 安全使用。如果一个类型提供了更强的保证,文档注释应该说明它们。例如
package regexp
// Regexp is the representation of a compiled regular expression.
// A Regexp is safe for concurrent use by multiple goroutines,
// except for configuration methods, such as Longest.
type Regexp struct {
...
}
Go 类型还应该努力使零值具有有用的含义。如果这一点不明显,则应该记录该含义。例如
package bytes
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
...
}
对于具有导出字段的结构体,文档注释或每个字段的注释应该解释每个导出字段的含义。例如,这个类型的文档注释解释了这些字段
package io
// A LimitedReader reads from R but limits the amount of
// data returned to just N bytes. Each call to Read
// updates N to reflect the new amount remaining.
// Read returns EOF when N <= 0.
type LimitedReader struct {
R Reader // underlying reader
N int64 // max bytes remaining
}
相比之下,这个类型的文档注释将解释留给了每个字段的注释
package comment
// A Printer is a doc comment printer.
// The fields in the struct can be filled in before calling
// any of the printing methods
// in order to customize the details of the printing process.
type Printer struct {
// HeadingLevel is the nesting level used for
// HTML and Markdown headings.
// If HeadingLevel is zero, it defaults to level 3,
// meaning to use <h3> and ###.
HeadingLevel int
...
}
与包(上文)和函数(下文)一样,类型的文档注释以命名所声明符号的完整句子开头。明确的主语通常使措辞更清晰,并且使文本更容易搜索,无论是在网页上还是在命令行上。例如
$ go doc -all regexp | grep pairs
pairs within the input string: result[2*n:2*n+2] identifies the indexes
FindReaderSubmatchIndex returns a slice holding the index pairs identifying
FindStringSubmatchIndex returns a slice holding the index pairs identifying
FindSubmatchIndex returns a slice holding the index pairs identifying the
$
函数
函数的文档注释应该解释函数返回什么,或者对于因副作用而调用的函数,解释它做什么。命名的参数和结果可以直接在注释中引用,无需反引号等特殊语法。(此约定的一个结果是,通常避免使用像 a
这样可能被误认为是普通词汇的名称。)例如
package strconv
// Quote returns a double-quoted Go string literal representing s.
// The returned string uses Go escape sequences (\t, \n, \xFF, \u0100)
// for control characters and non-printable characters as defined by IsPrint.
func Quote(s string) string {
...
}
并且
package os
// Exit causes the current program to exit with the given status code.
// Conventionally, code zero indicates success, non-zero an error.
// The program terminates immediately; deferred functions are not run.
//
// For portability, the status code should be in the range [0, 125].
func Exit(code int) {
...
}
文档注释通常使用短语“reports whether”来描述返回布尔值的函数。短语“or not”是不必要的。例如
package strings
// HasPrefix reports whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool
如果文档注释需要解释多个结果,命名结果可以使文档注释更容易理解,即使这些名称未在函数体中使用。例如
package io
// Copy copies from src to dst until either EOF is reached
// on src or an error occurs. It returns the total number of bytes
// written and the first error encountered while copying, if any.
//
// A successful Copy returns err == nil, not err == EOF.
// Because Copy is defined to read from src until EOF, it does
// not treat an EOF from Read as an error to be reported.
func Copy(dst Writer, src Reader) (n int64, err error) {
...
}
相反,当结果不需要在文档注释中命名时,它们通常在代码中也省略,就像上面的 Quote
示例一样,以避免使呈现内容杂乱。
这些规则适用于普通函数和方法。对于方法,使用相同的接收器名称可以避免在列出类型的所有方法时不必要的变动
$ go doc bytes.Buffer
package bytes // import "bytes"
type Buffer struct {
// Has unexported fields.
}
A Buffer is a variable-sized buffer of bytes with Read and Write methods.
The zero value for Buffer is an empty buffer ready to use.
func NewBuffer(buf []byte) *Buffer
func NewBufferString(s string) *Buffer
func (b *Buffer) Bytes() []byte
func (b *Buffer) Cap() int
func (b *Buffer) Grow(n int)
func (b *Buffer) Len() int
func (b *Buffer) Next(n int) []byte
func (b *Buffer) Read(p []byte) (n int, err error)
func (b *Buffer) ReadByte() (byte, error)
...
这个例子还表明,返回类型 T
或指针 *T
的顶层函数(可能带有额外的错误结果)会与类型 T
及其方法一起显示,假设它们是 T
的构造函数。
默认情况下,程序员可以假定顶层函数可以安全地从多个 goroutine 调用;此事实无需明确说明。
另一方面,如上一节所述,以任何方式使用类型的实例,包括调用方法,通常假定限制在同一时间由单个 goroutine 进行。如果那些可以安全地用于并发使用的方法未在类型的文档注释中记录,则应在每个方法的注释中记录。例如
package sql
// Close returns the connection to the connection pool.
// All operations after a Close will return with ErrConnDone.
// Close is safe to call concurrently with other operations and will
// block until all other operations finish. It may be useful to first
// cancel any used context and then call Close directly after.
func (c *Conn) Close() error {
...
}
请注意,函数和方法文档注释侧重于操作返回或做什么,详细说明调用者需要了解的内容。特殊情况可能尤其重要,需要文档记录。例如
package math
// Sqrt returns the square root of x.
//
// Special cases are:
//
// Sqrt(+Inf) = +Inf
// Sqrt(±0) = ±0
// Sqrt(x < 0) = NaN
// Sqrt(NaN) = NaN
func Sqrt(x float64) float64 {
...
}
文档注释不应该解释内部细节,例如当前实现中使用的算法。这些最好留在函数体内部的注释中。当该细节对调用者特别重要时,可能适合给出渐近时间或空间界限。例如
package sort
// Sort sorts data in ascending order as determined by the Less method.
// It makes one call to data.Len to determine n and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
...
}
由于此文档注释没有提及使用了哪种排序算法,因此将来更容易更改实现以使用不同的算法。
常量
Go 的声明语法允许将声明分组,在这种情况下,单个文档注释可以介绍一组相关的常量,单个常量仅通过简短的行尾注释进行文档记录。例如
package scanner // import "text/scanner"
// The result of Scan is one of these tokens or a Unicode character.
const (
EOF = -(iota + 1)
Ident
Int
Float
Char
...
)
有时,分组根本不需要文档注释。例如
package unicode // import "unicode"
const (
MaxRune = '\U0010FFFF' // maximum valid Unicode code point.
ReplacementChar = '\uFFFD' // represents invalid code points.
MaxASCII = '\u007F' // maximum ASCII value.
MaxLatin1 = '\u00FF' // maximum Latin-1 value.
)
另一方面,未分组的常量通常需要以完整句子开头的完整文档注释。例如
package unicode
// Version is the Unicode edition from which the tables are derived.
const Version = "13.0.0"
带类型的常量显示在其类型声明旁边,因此通常省略常量分组文档注释,而倾向于使用类型的文档注释。例如
package syntax
// An Op is a single regular expression operator.
type Op uint8
const (
OpNoMatch Op = 1 + iota // matches no strings
OpEmptyMatch // matches empty string
OpLiteral // matches Runes sequence
OpCharClass // matches Runes interpreted as range pair list
OpAnyCharNotNL // matches any character except newline
...
)
(有关 HTML 呈现,请参见 pkg.go.dev/regexp/syntax#Op。)
变量
变量的约定与常量的约定相同。例如,这是一组分组的变量
package fs
// Generic file system errors.
// Errors returned by file systems can be tested against these errors
// using errors.Is.
var (
ErrInvalid = errInvalid() // "invalid argument"
ErrPermission = errPermission() // "permission denied"
ErrExist = errExist() // "file already exists"
ErrNotExist = errNotExist() // "file does not exist"
ErrClosed = errClosed() // "file already closed"
)
以及一个单独的变量
package unicode
// Scripts is the set of Unicode script tables.
var Scripts = map[string]*RangeTable{
"Adlam": Adlam,
"Ahom": Ahom,
"Anatolian_Hieroglyphs": Anatolian_Hieroglyphs,
"Arabic": Arabic,
"Armenian": Armenian,
...
}
语法
Go 文档注释使用一种简单的语法编写,支持段落、标题、链接、列表和预格式化代码块。为了使注释轻量级并在源文件中易于阅读,不支持字体更改或原始 HTML 等复杂功能。Markdown 爱好者可以将该语法视为 Markdown 的简化子集。
标准格式化工具 gofmt 会重新格式化文档注释,使其采用每种功能的规范格式。Gofmt 旨在提高可读性和用户对源文件中注释编写方式的控制,但会调整呈现方式以使特定注释的语义含义更清晰,这类似于将普通源代码中的 1+2 * 3
重新格式化为 1 + 2*3
。
指令注释,例如 //go:generate
,不被视为文档注释的一部分,并从渲染的文档中省略。Gofmt 将指令注释移动到文档注释的末尾,前面有一个空行。例如
package regexp
// An Op is a single regular expression operator.
//
//go:generate stringer -type Op -trimprefix Op
type Op uint8
指令注释是匹配正则表达式 //(line |extern |export |[a-z0-9]+:[a-z0-9])
的行。定义自己指令的工具应该使用 //toolname:directive
的形式。
Gofmt 会删除文档注释开头和结尾的空行。如果文档注释中的所有行都以相同的空格和制表符序列开头,gofmt 会删除该前缀。
段落
段落是一段未缩进的非空行。我们已经看到了很多段落的例子。
一对连续的反引号 (` U+0060) 被解释为 Unicode 左引号 (“ U+201C),一对连续的单引号 (' U+0027) 被解释为 Unicode 右引号 (” U+201D)。
Gofmt 保留段落文本中的换行符:它不会重新换行文本。这允许使用语义换行,如前所述。Gofmt 将段落之间重复的空行替换为单个空行。Gofmt 还会将连续的反引号或单引号重新格式化为它们的 Unicode 解释。
备注
备注是形式为 MARKER(uid): body
的特殊注释。MARKER 应由 2 个或更多大写 [A-Z]
字母组成,用于标识备注的类型,而 uid 至少有 1 个字符,通常是能提供更多信息的人员的用户名。uid 后面的 :
是可选的。
备注会在 pkg.go.dev 上收集并呈现在自己的部分。
例如
// TODO(user1): refactor to use standard library context
// BUG(user2): not cleaned up
var ctx context.Context
弃用
以 Deprecated:
开头的段落被视为弃用通知。某些工具会在使用弃用标识符时发出警告。pkg.go.dev 默认会隐藏其文档。
弃用通知后跟有关弃用的一些信息,以及关于改用什么的建议(如果适用)。该段落不必是文档注释中的最后一个段落。
例如
// Package rc4 implements the RC4 stream cipher.
//
// Deprecated: RC4 is cryptographically broken and should not be used
// except for compatibility with legacy systems.
//
// This package is frozen and no new functionality will be added.
package rc4
// Reset zeros the key data and makes the Cipher unusable.
//
// Deprecated: Reset can't guarantee that the key will be entirely removed from
// the process's memory.
func (c *Cipher) Reset()
标题
标题是以下划线号 (U+0023) 开头,后跟一个空格和标题文本的行。要被识别为标题,该行必须未缩进,并且与相邻的段落文本之间有空行隔开。
例如
// Package strconv implements conversions to and from string representations
// of basic data types.
//
// # Numeric Conversions
//
// The most common numeric conversions are [Atoi] (string to int) and [Itoa] (int to string).
...
package strconv
另一方面
// #This is not a heading, because there is no space.
//
// # This is not a heading,
// # because it is multiple lines.
//
// # This is not a heading,
// because it is also multiple lines.
//
// The next paragraph is not a heading, because there is no additional text:
//
// #
//
// In the middle of a span of non-blank lines,
// # this is not a heading either.
//
// # This is not a heading, because it is indented.
# 语法是在 Go 1.19 中添加的。在 Go 1.19 之前,标题是通过满足特定条件(最显著的是缺乏任何结束标点)的单行段落隐式识别的。
Gofmt 重新格式化了被早期 Go 版本视为隐式标题的行,转而使用 # 标题。如果重新格式化不合适——也就是说,如果该行并非 intended 作为标题——最简单的方法是引入结束标点,例如句号或冒号,或者将其分成两行,使其成为段落。
链接
一段未缩进的非空行定义了链接目标,当每行的形式为 “[文本]: URL” 时。在同一文档注释的其他文本中,“[文本]”表示使用给定文本指向 URL 的链接——在 HTML 中,即 <a href=“URL”>文本</a>。例如
// Package json implements encoding and decoding of JSON as defined in
// [RFC 7159]. The mapping between JSON and Go values is described
// in the documentation for the Marshal and Unmarshal functions.
//
// For an introduction to this package, see the article
// “[JSON and Go].”
//
// [RFC 7159]: https://tools.ietf.org/html/rfc7159
// [JSON and Go]: https://golang.ac.cn/doc/articles/json_and_go.html
package json
通过将 URL 保留在单独的部分,这种格式对实际文本流程的干扰最小。它也大致匹配 Markdown 的快捷引用链接格式,但不包含可选的标题文本。
如果没有相应的 URL 声明,则(文档链接除外,这将在下一节描述)“[文本]”不是超链接,并且在显示时保留方括号。每个文档注释被视为独立的:一个注释中的链接目标定义不影响其他注释。
虽然链接目标定义块可以与普通段落交错,但 gofmt 会将所有链接目标定义移动到文档注释的末尾,最多分为两个块:第一个块包含注释中引用的所有链接目标,然后一个块包含注释中未引用的所有目标。单独的块使得未使用的目标易于注意到并进行修复(如果链接或定义有拼写错误)或删除(如果定义不再需要)。
被识别为 URL 的纯文本会在 HTML 渲染中自动链接。
文档链接
文档链接是指向当前包中导出标识符的 “[名称1]” 或 “[名称1.名称2]” 形式的链接,或者是指向其他包中标识符的 “[包名]”、“[包名.名称1]” 或 “[包名.名称1.名称2]” 形式的链接。
例如
package bytes
// ReadFrom reads data from r until EOF and appends it to the buffer, growing
// the buffer as needed. The return value n is the number of bytes read. Any
// error except [io.EOF] encountered during the read is also returned. If the
// buffer becomes too large, ReadFrom will panic with [ErrTooLarge].
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
...
}
符号链接的方括号文本可以包含可选的前导星号,以便于引用指针类型,例如 [*bytes.Buffer]。
在引用其他包时,“包名”可以是完整的导入路径,也可以是现有导入的假定包名。假定包名是重命名导入中的标识符,或者是 goimports 假定的名称。(当该假定不正确时,Goimports 会插入重命名,因此此规则基本上适用于所有 Go 代码。)例如,如果当前包导入了 encoding/json,则可以使用 “[json.Decoder]” 代替 “[encoding/json.Decoder]” 来链接到 encoding/json 的 Decoder 文档。如果包中不同的源文件使用相同的名称导入不同的包,则这种缩写是模糊的,不能使用。
只有当“包名”以域名(带有点的路径元素)开头或它是标准库中的包(“[os]”、“[encoding/json]” 等)时,才被假定为完整的导入路径。例如,[os.File]
和 [example.com/sys.File]
是文档链接(后者将是无效链接),但 [os/sys.File]
不是,因为标准库中没有 os/sys 包。
为了避免映射、泛型和数组类型的问题,文档链接必须前后都有标点、空格、制表符或位于行的开头或结尾。例如,文本“map[ast.Expr]TypeAndValue”不包含文档链接。
列表
列表是一段缩进或空行(否则将是代码块,如下一节所述),其中第一行缩进的行以项目符号列表标记或编号列表标记开头。
项目符号列表标记是星号、加号、破折号或 Unicode 项目符号(*, +, -, •; U+002A, U+002B, U+002D, U+2022),后跟一个空格或制表符,然后是文本。在项目符号列表中,每行以项目符号列表标记开头都会开始一个新的列表项。
例如
package url
// PublicSuffixList provides the public suffix of a domain. For example:
// - the public suffix of "example.com" is "com",
// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and
// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us".
//
// Implementations of PublicSuffixList must be safe for concurrent use by
// multiple goroutines.
//
// An implementation that always returns "" is valid and may be useful for
// testing but it is not secure: it means that the HTTP server for foo.com can
// set a cookie for bar.com.
//
// A public suffix list implementation is in the package
// golang.org/x/net/publicsuffix.
type PublicSuffixList interface {
...
}
编号列表标记是任意长度的十进制数字,后跟句号或右括号,然后是空格或制表符,然后是文本。在编号列表中,每行以编号列表标记开头都会开始一个新的列表项。项目编号保持原样,从不重新编号。
例如
package path
// Clean returns the shortest path name equivalent to path
// by purely lexical processing. It applies the following rules
// iteratively until no further processing can be done:
//
// 1. Replace multiple slashes with a single slash.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path.
//
// The returned path ends in a slash only if it is the root "/".
//
// If the result of this process is an empty string, Clean
// returns the string ".".
//
// See also Rob Pike, “[Lexical File Names in Plan 9].”
//
// [Lexical File Names in Plan 9]: https://9p.io/sys/doc/lexnames.html
func Clean(path string) string {
...
}
列表项只包含段落,不包含代码块或嵌套列表。这避免了任何空间计数上的微妙之处,以及关于在不一致缩进中一个制表符算作多少个空格的问题。
Gofmt 重新格式化项目符号列表,使用破折号作为项目符号标记,破折号前缩进两个空格,连续行缩进四个空格。
Gofmt 重新格式化编号列表,在数字前使用一个空格,数字后使用一个句号,连续行再次缩进四个空格。
Gofmt 保留但不要求列表与前面的段落之间有一个空行。它在列表与后面的段落或标题之间插入一个空行。
代码块
代码块是一段缩进或空行,不以项目符号列表标记或编号列表标记开头。它被渲染为预格式化文本(在 HTML 中是一个 <pre> 块)。
代码块通常包含 Go 代码。例如
package sort
// Search uses binary search...
//
// As a more whimsical example, this program guesses your number:
//
// func GuessingGame() {
// var s string
// fmt.Printf("Pick an integer from 0 to 100.\n")
// answer := sort.Search(100, func(i int) bool {
// fmt.Printf("Is your number <= %d? ", i)
// fmt.Scanf("%s", &s)
// return s != "" && s[0] == 'y'
// })
// fmt.Printf("Your number is %d.\n", answer)
// }
func Search(n int, f func(int) bool) int {
...
}
当然,代码块通常也包含除代码以外的预格式化文本。例如
package path
// Match reports whether name matches the shell pattern.
// The pattern syntax is:
//
// pattern:
// { term }
// term:
// '*' matches any sequence of non-/ characters
// '?' matches any single non-/ character
// '[' [ '^' ] { character-range } ']'
// character class (must be non-empty)
// c matches character c (c != '*', '?', '\\', '[')
// '\\' c matches character c
//
// character-range:
// c matches character c (c != '\\', '-', ']')
// '\\' c matches character c
// lo '-' hi matches character c for lo <= c <= hi
//
// Match requires pattern to match all of name, not just a substring.
// The only possible returned error is [ErrBadPattern], when pattern
// is malformed.
func Match(pattern, name string) (matched bool, err error) {
...
}
Gofmt 将代码块中的所有行缩进一个制表符,替换掉非空行共有的任何其他缩进。Gofmt 还在每个代码块的前后插入一个空行,将代码块与周围的段落文本明确区分开来。
常见错误和陷阱
文档注释中任何一段缩进或空行都会被渲染为代码块的规则可以追溯到 Go 的早期。不幸的是,gofmt 对文档注释的支持不足导致许多现有的注释使用缩进并非意在创建代码块。
例如,这个未缩进的列表总是被 godoc 解释为一个三行段落后跟一个单行代码块
package http
// cancelTimerBody is an io.ReadCloser that wraps rc with two features:
// 1) On Read error or close, the stop func is called.
// 2) On Read failure, if reqDidTimeout is true, the error is wrapped and
// marked as net.Error that hit its timeout.
type cancelTimerBody struct {
...
}
在 go
doc
中总是这样渲染
cancelTimerBody is an io.ReadCloser that wraps rc with two features:
1) On Read error or close, the stop func is called. 2) On Read failure,
if reqDidTimeout is true, the error is wrapped and
marked as net.Error that hit its timeout.
同样,这个注释中的命令是一个单行段落后跟一个单行代码块
package smtp
// localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
//
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
// --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`...`)
在 go
doc
中这样渲染
localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
--ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
这个注释是一个两行段落(第二行是“{”),后跟一个六行缩进的代码块和一个单行段落(“}”)。
// On the wire, the JSON will look something like this:
// {
// "kind":"MyAPIObject",
// "apiVersion":"v1",
// "myPlugin": {
// "kind":"PluginA",
// "aOption":"foo",
// },
// }
在 go
doc
中这样渲染
On the wire, the JSON will look something like this: {
"kind":"MyAPIObject",
"apiVersion":"v1",
"myPlugin": {
"kind":"PluginA",
"aOption":"foo",
},
}
另一个常见错误是未缩进的 Go 函数定义或块语句,类似地由“{”和“}”括起来。
Go 1.19 的 gofmt 中引入的文档注释重新格式化功能通过在代码块周围添加空行,使得这类错误更加明显。
2022 年的分析发现,草案版 Go 1.19 gofmt 对公共 Go 模块中只有 3% 的文档注释进行了重新格式化。仅限于这些注释,gofmt 大约 87% 的重新格式化保留了人们通过阅读注释推断出的结构;大约 6% 的格式化被这些类型的未缩进列表、未缩进多行 shell 命令以及未缩进的大括号分隔的代码块所困扰。
基于此分析,Go 1.19 gofmt 应用了一些启发式方法,将未缩进的行合并到相邻的缩进列表或代码块中。经过这些调整,Go 1.19 gofmt 将上述示例重新格式化为
// cancelTimerBody is an io.ReadCloser that wraps rc with two features:
// 1. On Read error or close, the stop func is called.
// 2. On Read failure, if reqDidTimeout is true, the error is wrapped and
// marked as net.Error that hit its timeout.
// localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
//
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
// --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
// On the wire, the JSON will look something like this:
//
// {
// "kind":"MyAPIObject",
// "apiVersion":"v1",
// "myPlugin": {
// "kind":"PluginA",
// "aOption":"foo",
// },
// }
这种重新格式化使含义更清晰,同时也使文档注释在 Go 的早期版本中正确渲染。如果启发式方法做出了错误的判断,可以通过插入一个空行来明确区分段落文本和非段落文本,从而覆盖它。
即使有了这些启发式方法,其他现有注释仍需要手动调整以纠正其渲染。最常见的错误是将换行的未缩进文本行缩进。例如
// TODO Revisit this design. It may make sense to walk those nodes
// only once.
// According to the document:
// "The alignment factor (in bytes) that is used to align the raw data of sections in
// the image file. The value should be a power of 2 between 512 and 64 K, inclusive."
在这两个例子中,最后一行都缩进了,使其成为一个代码块。解决方案是取消行的缩进。
另一个常见错误是没有缩进列表或代码块的换行缩进行。例如
// Uses of this error model include:
//
// - Partial errors. If a service needs to return partial errors to the
// client,
// it may embed the `Status` in the normal response to indicate the
// partial
// errors.
//
// - Workflow errors. A typical workflow has multiple steps. Each step
// may
// have a `Status` message for error reporting.
解决方案是缩进换行的行。
Go 文档注释不支持嵌套列表,因此 gofmt 会重新格式化
// Here is a list:
//
// - Item 1.
// * Subitem 1.
// * Subitem 2.
// - Item 2.
// - Item 3.
为
// Here is a list:
//
// - Item 1.
// - Subitem 1.
// - Subitem 2.
// - Item 2.
// - Item 3.
重写文本以避免嵌套列表通常会改善文档,是最好的解决方案。另一种潜在的解决方法是混合使用列表标记,因为项目符号标记不会在编号列表中引入列表项,反之亦然。例如
// Here is a list:
//
// 1. Item 1.
//
// - Subitem 1.
//
// - Subitem 2.
//
// 2. Item 2.
//
// 3. Item 3.