Go Doc コメント
目次
パッケージ
コマンド
型
関数
定数
変数
構文
よくある間違いと落とし穴
「Doc コメント」とは、トップレベルのパッケージ、定数、関数、型、および変数の宣言の直前で、改行を挟まずに記述されるコメントです。エクスポートされる(大文字で始まる)すべての名前には、Doc コメントが必要です。
go/doc および go/doc/comment パッケージは、Go ソースコードからドキュメントを抽出する機能を提供し、さまざまなツールがこの機能を利用しています。go doc コマンドは、指定されたパッケージまたはシンボルの Doc コメントを検索して表示します。(シンボルとは、トップレベルの定数、関数、型、または変数です。)Web サーバー pkg.go.dev は、公開されている Go パッケージのドキュメントを表示します(ライセンスがその使用を許可している場合)。このサイトをホストしているプログラムは golang.org/x/pkgsite/cmd/pkgsite であり、ローカルで実行してプライベートモジュールのドキュメントを表示したり、インターネット接続なしで表示することもできます。言語サーバー gopls は、IDE で Go ソースファイルを編集する際にドキュメントを提供します。
このページの残りの部分では、Go の Doc コメントの書き方について説明します。
パッケージ
すべてのパッケージには、パッケージを紹介するパッケージコメントが必要です。これはパッケージ全体に関連する情報を提供し、一般的にパッケージに対する期待を設定します。特に大きなパッケージでは、パッケージコメントが API の最も重要な部分の簡単な概要を提供し、必要に応じて他の Doc コメントにリンクすることが役立ちます。
パッケージが単純な場合、パッケージコメントは簡潔で構いません。例えば
// 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 の Doc コメントは完全な文を使用します。パッケージコメントの場合、これは最初の文が「Package」で始まることを意味します。
複数ファイルのパッケージの場合、パッケージコメントは1つのソースファイルのみに記述する必要があります。複数のファイルにパッケージコメントがある場合、それらは連結されてパッケージ全体の1つの大きなコメントを形成します。
コマンド
コマンドのパッケージコメントも同様ですが、パッケージ内の 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
コメントの冒頭はセマンティック改行を使用して書かれており、各新しい文または長いフレーズはそれ自体で1行に記述されているため、コードとコメントの進化に伴う差分を読みやすくすることができます。それ以降の段落はこの慣習に従っておらず、手作業で折り返されています。あなたのコードベースに最適な方法であれば問題ありません。いずれにしても、go doc と pkgsite は、印刷時に Doc コメントのテキストを再度折り返します。例えば
$ 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 表示ではコードフォントで表示されます。(以下の構文セクションで詳細を説明します。)
型
型の Doc コメントは、その型の各インスタンスが何を表すか、または何を提供するべきかを説明する必要があります。API が単純な場合、Doc コメントはかなり短くても構いません。例えば
package zip
// A Reader serves content from a ZIP archive.
type Reader struct {
...
}
デフォルトでは、プログラマーは型が一度に単一のゴルーチンによってのみ安全に使用できると考えるべきです。型がより強力な保証を提供する場合は、Doc コメントにそれらを記述する必要があります。例えば
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 {
...
}
エクスポートされたフィールドを持つ構造体の場合、Doc コメントまたはフィールドごとのコメントのいずれかで、各エクスポートされたフィールドの意味を説明する必要があります。例えば、この型の Doc コメントはフィールドを説明しています。
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
}
対照的に、この型の Doc コメントは、説明をフィールドごとのコメントに任せています。
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
...
}
パッケージ(上記)や関数(下記)と同様に、型の Doc コメントは宣言されたシンボルの名前を含む完全な文で始まります。明示的な主語は、言葉遣いをより明確にし、ウェブページでもコマンドラインでもテキストを検索しやすくします。例えば
$ 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
$
関数
関数の Doc コメントは、関数が何を返すか、または副作用のために呼び出される関数の場合は何をするかを説明する必要があります。名前付きパラメータと結果は、バッククォートなどの特別な構文なしでコメント内で直接参照できます。(この慣習の結果として、通常の単語と間違われる可能性のある `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) {
...
}
Doc コメントでは、真偽値を返す関数を説明する際に「reports whether」というフレーズがよく使われます。「or not」というフレーズは不要です。例えば
package strings
// HasPrefix reports whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool
Doc コメントで複数の結果を説明する必要がある場合、たとえ関数の本体で名前が使用されていなくても、結果に名前を付けることで Doc コメントをより理解しやすくすることができます。例えば
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) {
...
}
逆に、Doc コメントで結果に名前を付ける必要がない場合は、プレゼンテーションを邪魔しないように、上記の `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` とそのメソッドと一緒に表示されることを示しています。
デフォルトでは、プログラマーはトップレベルの関数が複数のゴルーチンから安全に呼び出せると仮定できます。この事実を明示的に記述する必要はありません。
一方、前のセクションで述べたように、型のインスタンスを使用する(メソッドの呼び出しを含む)場合は、通常、一度に単一のゴルーチンに制限されると仮定されます。同時使用に安全なメソッドが型の Doc コメントに文書化されていない場合は、メソッドごとのコメントに文書化する必要があります。例えば
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 {
...
}
関数およびメソッドの Doc コメントは、操作が何を返すか、または何をするかに焦点を当て、呼び出し元が知る必要がある詳細を記述することに注意してください。特殊なケースは特に文書化することが重要です。例えば
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 {
...
}
Doc コメントでは、現在の実装で使用されているアルゴリズムなどの内部的な詳細を説明すべきではありません。これらは関数本体内のコメントに残すのが最善です。呼び出し元にとってその詳細が特に重要な場合、漸近的な時間または空間の制約を示すことは適切かもしれません。例えば
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) {
...
}
この Doc コメントでは、どのソートアルゴリズムが使用されているかについては言及されていないため、将来的に実装を変更して別のアルゴリズムを使用することが容易になります。
定数
Go の宣言構文では、宣言のグループ化が可能です。この場合、1つの Doc コメントで関連する定数のグループを導入し、個々の定数は短い行末コメントでのみ文書化できます。例えば
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
...
)
グループにはDocコメントが全く不要な場合もあります。例えば
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.
)
一方、グループ化されていない定数は、通常、完全な文で始まる完全なDocコメントが必要です。例えば
package unicode
// Version is the Unicode edition from which the tables are derived.
const Version = "13.0.0"
型付き定数は、その型の宣言の隣に表示されるため、型グループの Doc コメントは省略され、型の Doc コメントが優先されることがよくあります。例えば
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 の Doc コメントは、段落、見出し、リンク、リスト、整形済みコードブロックをサポートするシンプルな構文で記述されています。コメントを軽量に保ち、ソースファイル内で読みやすくするために、フォントの変更や生の HTML のような複雑な機能はサポートされていません。Markdown の愛好家は、この構文を Markdown の簡略化されたサブセットと見なすことができます。
標準フォーマッターである gofmt は、これらの各機能に対して正規の書式設定を使用するように Doc コメントを整形します。Gofmt は、ソースコードでのコメントの記述方法に対する可読性とユーザー制御を目指しますが、通常のソースコードで `1+2 * 3` を `1 + 2*3` に整形するのと同様に、特定のコメントのセマンティックな意味をより明確にするために表現を調整します。
//go:generate のようなディレクティブコメントは Doc コメントの一部とは見なされず、レンダリングされたドキュメントからは省略されます。Gofmt はディレクティブコメントを Doc コメントの末尾に移動させ、その前に空白行を置きます。例えば
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 は Doc コメント内の先頭と末尾の空白行を削除します。Doc コメント内のすべての行が同じスペースとタブのシーケンスで始まる場合、gofmt はそのプレフィックスを削除します。
段落
段落とは、インデントされていない空白ではない行の連続です。すでに多くの段落の例を見てきました。
2つの連続したバッククォート (`` U+0060) は Unicode の左引用符 (「 U+201C) として解釈され、2つの連続したシングルクォート ('' 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 は、デフォルトでそれらのドキュメントを非表示にします。
非推奨通知には、非推奨に関する情報と、該当する場合は代わりに使用すべきものの推奨が続きます。この段落は Doc コメントの最後の段落である必要はありません。
例
// 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 のバージョンで暗黙の見出しとして扱われた行を # 見出しを使用するように再フォーマットします。もし再フォーマットが適切でない場合(つまり、その行が見出しとして意図されていなかった場合)、それを段落にする最も簡単な方法は、ピリオドやコロンなどの終止符を導入するか、2行に分割することです。
リンク
インデントされていない空白ではない行の連続は、すべての行が「[Text]: URL」の形式である場合にリンクターゲットを定義します。同じ Doc コメント内の他のテキストでは、「[Text]」は、指定されたテキストを使用して URL へのリンクを表します(HTML では <a href=“URL”>Text</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://go.dokyumento.jp/doc/articles/json_and_go.html
package json
URL を別のセクションに保持することで、このフォーマットは実際のテキストの流れを最小限に妨げるだけです。また、オプションのタイトルテキストなしで、Markdown のショートカット参照リンクフォーマットとほぼ一致します。
対応する URL 宣言がない場合、(次のセクションで説明する Doc リンクを除いて)「[Text]」はハイパーリンクではなく、表示時に角括弧が保持されます。各 Doc コメントは独立して考慮されます。あるコメント内のリンクターゲット定義は、他のコメントに影響しません。
リンクターゲット定義ブロックは通常の段落と混在できますが、gofmt はすべてのリンクターゲット定義を Doc コメントの最後に、最大2つのブロックに移動します。最初にコメントで参照されているすべてのリンクターゲットを含むブロック、次にコメントで参照**されていない**すべてのターゲットを含むブロックです。この分離されたブロックにより、未使用のターゲットは簡単に気づき、修正(リンクまたは定義にタイプミスがある場合)または削除(定義が不要になった場合)できます。
URL として認識されるプレーンテキストは、HTML レンダリングで自動的にリンクされます。
Doc リンク
Doc リンクは、現在のパッケージ内のエクスポートされた識別子を参照するための「[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 をインポートしている場合、「[encoding/json.Decoder]」の代わりに「[json.Decoder]」と記述して、encoding/json の Decoder のドキュメントにリンクできます。パッケージ内の異なるソースファイルが同じ名前を使用して異なるパッケージをインポートしている場合、ショートハンドはあいまいであり、使用できません。
「pkg」が完全なインポートパスであると仮定されるのは、それがドメイン名(ドットを含むパス要素)で始まるか、標準ライブラリのパッケージ(「[os]」、「[encoding/json]」など)のいずれかである場合のみです。たとえば、[os.File] と [example.com/sys.File] はドキュメントリンクですが(後者は壊れたリンクになります)、[os/sys.File] は標準ライブラリに os/sys パッケージがないため、ドキュメントリンクではありません。
マップ、ジェネリクス、および配列型に関する問題を回避するため、Doc リンクは句読点、スペース、タブ、または行の開始/終了によって前後に区切られる必要があります。たとえば、テキスト「map[ast.Expr]TypeAndValue」には Doc リンクは含まれていません。
リスト
リストとは、インデントされた行または空白行(次のセクションで説明するように、そうでなければコードブロックになる)で、最初のインデントされた行が箇条書きマーカーまたは番号付きリストマーカーで始まるものです。
箇条書きリストマーカーは、アスタリスク、プラス、ダッシュ、またはユニコードの箇条書き(*, +, -, •; 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 {
...
}
番号付きリストマーカーは、任意の長さの10進数にピリオドまたは右括弧、スペースまたはタブ、そしてテキストが続くものです。番号付きリストでは、番号付きリストマーカーで始まる各行が新しいリスト項目を開始します。項目番号はそのまま残され、再番号付けされることはありません。
例
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 は、箇条書きリストをダッシュを箇条書きマーカーとして使用し、ダッシュの前に2スペースのインデント、継続行には4スペースのインデントを使用するように整形します。
Gofmt は、番号付きリストを番号の前に1スペース、番号の後にピリオド、継続行には再び4スペースのインデントを使用するように整形します。
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 はまた、各コードブロックの前後にも空白行を挿入し、コードブロックを周囲の段落テキストと明確に区別します。
よくある間違いと落とし穴
Doc コメント内のインデントされた行または空白行の連続がコードブロックとしてレンダリングされるというルールは、Go の初期の頃から存在します。残念ながら、gofmt で Doc コメントがサポートされていなかったため、コードブロックを作成する意図なしにインデントを使用している既存のコメントが多数存在します。
たとえば、このインデントされていないリストは、常に godoc によって3行の段落と1行のコードブロックとして解釈されてきました。
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.
同様に、このコメント内のコマンドは、1行の段落とそれに続く1行のコードブロックです。
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
そしてこのコメントは、2行の段落(2行目は「{」)、それに続く6行のインデントされたコードブロック、そして1行の段落(「}」)です。
// 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 で Doc コメントの整形機能が導入されたことで、コードブロックの周りに空白行が追加され、このような間違いがより目立つようになりました。
2022年の分析では、公開されているGoモジュール内のDocコメントのうち、ドラフト版のGo 1.19 gofmtによって整形されたものはわずか3%でした。その整形されたコメントに限定すると、gofmtの整形の約87%は、人がコメントを読んだときに推測する構造を維持していました。約6%は、このようなインデントされていないリスト、インデントされていない複数行のシェルコマンド、およびインデントされていない中括弧で区切られたコードブロックによって引っかかっていました。
この分析に基づいて、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",
// },
// }
この整形により、意味が明確になるだけでなく、Doc コメントが以前の 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 の Doc コメントはネストされたリストをサポートしていないため、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.
テキストを書き換えてネストされたリストを避けることは、通常、ドキュメントを改善し、最善の解決策です。もう1つの潜在的な回避策は、箇条書きマーカーが番号付きリストにリスト項目を導入しない(逆も同様)ため、リストマーカーを混ぜることです。例えば
// Here is a list:
//
// 1. Item 1.
//
// - Subitem 1.
//
// - Subitem 2.
//
// 2. Item 2.
//
// 3. Item 3.