Go 文件註釋

目錄

軟體包
命令
型別
函式
常量
變數
語法
常見錯誤和陷阱

“文件註釋”是緊接在頂層包、常量、函式、型別和變數宣告之前,且中間沒有換行符的註釋。每個匯出的(大寫字母開頭的)名稱都應該有文件註釋。

go/docgo/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 docpkgsite 在列印時都會重新包裝文件註釋文字。例如:

$ 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
    ...
)

(請參閱 pkg.go.dev/regexp/syntax#Op 以獲取 HTML 呈現。)

變數

變數的約定與常量的約定相同。例如,這是一組分組變數:

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 版本中被視為隱式標題的行重新格式化為使用 # 標題。如果重新格式化不合適——也就是說,如果該行並非旨在作為標題——最簡單的方法是引入終止標點符號(例如句號或冒號),或者將其拆分成兩行,使其成為一個段落。

當每一行都採用“[Text]: URL”的形式時,一段不縮排的非空行定義了連結目標。在同一文件註釋中的其他文字中,“[Text]”表示使用給定文字連結到 URL——在 HTML 中,Text。例如:

// 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.org.tw/doc/articles/json_and_go.html
package json

透過將 URL 放在單獨的部分,這種格式只會最大限度地打斷實際文字的流暢性。它也大致符合 Markdown 快捷引用連結格式,不包含可選的標題文字。

如果不存在對應的 URL 宣告,那麼(除了下一節描述的文件連結)“[Text]”就不是超連結,並且方括號在顯示時會保留。每個文件註釋都是獨立考慮的:一個註釋中的連結目標定義不影響其他註釋。

雖然連結目標定義塊可以與普通段落交錯,但 gofmt 會將所有連結目標定義移動到文件註釋的末尾,最多分成兩個塊:第一個塊包含註釋中引用的所有連結目標,然後是包含註釋中*未*引用的所有目標的塊。單獨的塊使得未使用的目標易於發現和修復(如果連結或定義有拼寫錯誤)或刪除(如果定義不再需要)。

被識別為 URL 的純文字會在 HTML 渲染中自動連結。

文件連結的形式為“[Name1]”或“[Name1.Name2]”,用於引用當前包中匯出的識別符號;或者“[pkg]”、“[pkg.Name1]”或“[pkg.Name1.Name2]”,用於引用其他包中的識別符號。

例如

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]。

在引用其他包時,“pkg”可以是完整的匯入路徑,也可以是現有匯入的假定包名。假定包名要麼是重新命名匯入中的識別符號,要麼是 goimports 所假定的名稱。(Goimports 會在假定不正確時插入重新命名,因此此規則應適用於所有 Go 程式碼。)例如,如果當前包匯入 encoding/json,那麼 “[json.Decoder]” 可以代替 “[encoding/json.Decoder]” 連結到 encoding/json 的 Decoder 文件。如果包中的不同原始檔使用相同的名稱匯入不同的包,則縮寫會產生歧義,無法使用。

“pkg”僅在以域名(帶有點的路徑元素)開頭或屬於標準庫包(“[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 中的

 塊)。

程式碼塊通常包含 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 模組中只有 3% 的文件註釋被 Go 1.19 gofmt 草稿重新格式化過。僅限於這些註釋,大約 87% 的 gofmt 重新格式化保留了人們透過閱讀註釋推斷出的結構;大約 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.