Комментарии документации Go
Содержание:
Пакеты
Команды
Типы
Функции
Константы
Переменные
Синтаксис
Частые ошибки и подводные камни
«Комментарии документации» (doc comments) — это комментарии, которые располагаются непосредственно перед объявлениями пакетов, констант, функций, типов и переменных на верхнем уровне, без промежуточных разрывов строк. Каждое экспортируемое (с заглавной буквы) имя должно иметь комментарий документации.
Пакеты go/doc и go/doc/comment предоставляют возможность извлекать документацию из исходного кода Go, и различные инструменты используют эту функциональность. Команда go doc ищет и выводит комментарий документации для заданного пакета или символа. (Символ — это константа, функция, тип или переменная на верхнем уровне.) Веб-сервер pkg.go.dev отображает документацию для публичных Go пакетов (при условии, что их лицензии разрешают такое использование). Программа, обслуживающая этот сайт, — golang.org/x/pkgsite/cmd/pkgsite, которую также можно запустить локально для просмотра документации приватных модулей или без подключения к интернету. Языковой сервер gopls предоставляет документацию при редактировании исходных файлов Go в IDE.
Остальная часть этой страницы описывает, как писать комментарии документации в Go.
Пакеты
Каждый пакет должен иметь комментарий пакета, который представляет его. Он предоставляет информацию, относящуюся к пакету в целом, и обычно устанавливает ожидания относительно пакета. Особенно в больших пакетах может быть полезно, чтобы комментарий пакета давал краткий обзор наиболее важных частей API, при необходимости ссылаясь на другие комментарии документации.
Если пакет простой, комментарий пакета может быть кратким. Например:
<code>// 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 </code>
Квадратные скобки в [path/filepath] создают ссылку документации.
Как видно из этого примера, комментарии документации Go используют полные предложения. Для комментария пакета это означает, что первое предложение начинается с «Package
Для многофайловых пакетов комментарий пакета должен находиться только в одном исходном файле. Если несколько файлов содержат комментарии пакета, они объединяются в один большой комментарий для всего пакета.
Команды
Комментарий к пакету для команды похож на обычный комментарий к пакету, но он описывает поведение программы, а не символы Go в пакете. Первое предложение традиционно начинается с названия самой программы с заглавной буквы, поскольку оно находится в начале предложения. Например, вот сокращённая версия комментария к пакету для gofmt:
<code>/* Gofmt форматирует программы на Go. Он использует табуляцию для отступов и пробелы для выравнивания. Выравнивание предполагает, что редактор использует шрифт с фиксированной шириной. Без явного указания пути обрабатывает стандартный ввод. При передаче файла, работает с этим файлом; при передаче каталога — с файлами .go в этом каталоге рекурсивно. (Файлы, начинающиеся с точки, игнорируются.) По умолчанию gofmt выводит отформатированный исходный код в стандартный вывод. Использование: gofmt [флаги] [путь ...] Флаги: -d Не выводить отформатированный исходный код в стандартный вывод. Если форматирование файла отличается от форматирования gofmt, вывести различия в стандартный вывод. -w Не выводить отформатированный исходный код в стандартный вывод. Если форматирование файла отличается от форматирования gofmt, заменить его версией gofmt. Если при замене произошла ошибка, исходный файл восстанавливается из автоматического резервного копирования. Когда gofmt читает из стандартного ввода, он принимает либо полную программу на Go, либо фрагмент программы. Фрагмент программы должен быть синтаксически валидным списком объявлений, списком инструкций или выражением. При форматировании такого фрагмента gofmt сохраняет начальные отступы, а также начальные и конечные пробелы, чтобы отдельные части программы на Go можно было форматировать, перенаправляя их через gofmt. */ package main </code>
Начало комментария записано с использованием
семантических переводов строк,
при котором каждое новое предложение или длинная фраза находятся на отдельной строке,
что может облегчить чтение изменений при развитии кода и комментариев.
Последующие абзацы не следуют этой конвенции и были обёрнуты вручную.
Какой способ лучше подходит для вашей кодовой базы — не имеет значения.
В любом случае, go doc и pkgsite переносят текст комментариев к документации при его выводе.
Например:
<code>$ go doc gofmt Gofmt форматирует программы на Go. Он использует табуляцию для отступов и пробелы для выравнивания. Выравнивание предполагает, что редактор использует шрифт с фиксированной шириной. Без явного указания пути обрабатывает стандартный ввод. При передаче файла, работает с этим файлом; при передаче каталога — с файлами .go в этом каталоге рекурсивно. (Файлы, начинающиеся с точки, игнорируются.) По умолчанию, gofmt выводит отформатированный исходный код в стандартный вывод. Использование: gofmt [флаги] [путь ...] Флаги: -d Не выводить отформатированный исходный код в стандартный вывод. Если форматирование файла отличается от форматирования gofmt, вывести различия в стандартный вывод. ... </code>
Отступы строк рассматриваются как предварительно отформатированный текст: они не переносятся и выводятся моноширинным шрифтом в HTML и Markdown представлениях. (Раздел Синтаксис ниже содержит подробности.)
Типы
Документирующий комментарий к типу должен объяснять, что представляет собой каждый экземпляр этого типа или что он предоставляет. Если API простое, документирующий комментарий может быть довольно коротким. Например:
<code>package zip
// A Reader serves content from a ZIP archive.
type Reader struct {
...
}
</code>
По умолчанию программисты должны ожидать, что тип безопасен для использования только одной горутиной за раз. Если тип предоставляет более сильные гарантии, документирующий комментарий должен их указать. Например:
<code>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 {
...
}
</code>
Типы в Go также должны стремиться к тому, чтобы нулевое значение имело полезное значение. Если это не очевидно, такое значение должно быть задокументировано. Например:
<code>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 {
...
}
</code>
Для структуры с экспортируемыми полями, либо документирующий комментарий к типу, либо комментарии к каждому полю должны объяснять смысл каждого экспортируемого поля. Например, документирующий комментарий к этому типу объясняет поля:
<code>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
}
</code>
Напротив, документирующий комментарий к этому типу оставляет объяснения для комментариев к полям:
<code>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
...
}
</code>
Как и в случае с пакетами (выше) и функциями (ниже), документирующие комментарии к типам начинаются с полных предложений, указывающих объявленный символ. Явное подлежащее часто делает формулировку яснее и облегчает поиск текста, будь то веб-страница или командная строка. Например:
<code>$ 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 $ </code>
Функции
Документирующий комментарий к функции должен объяснять, что возвращает функция
или, для функций, вызываемых ради побочного эффекта, что она делает.
Именованные параметры и результаты могут быть упомянуты напрямую в
комментарии, без каких-либо специальных синтаксических конструкций, таких как обратные кавычки.
(Следствием этой конвенции является то, что имена вроде a,
которые могут быть спутаны с обычными словами, обычно избегаются.)
Например:
Пакет strconv
<code>// Quote возвращает двойные кавычки Go строкового литерала, представляющего s.
// Возвращённая строка использует Go escape-последовательности (\t, \n, \xFF, \u0100)
// для управляющих символов и непечатаемых символов, определённых функцией IsPrint.
func Quote(s string) string {
...
}
</code>
И:
<code>package os
// Exit заставляет текущую программу завершиться с указанным кодом состояния.
// Обычно, код ноль обозначает успешное завершение, ненулевой — ошибку.
// Программа завершается немедленно; отложенные функции не выполняются.
//
// Для обеспечения портируемости, код состояния должен находиться в диапазоне [0, 125].
func Exit(code int) {
...
}
</code>
Комментарии к документации обычно используют фразу «reports whether» для описания функций, возвращающих логическое значение. Фраза «or not» не требуется. Например:
<code>package strings // HasPrefix сообщает, начинается ли строка s с префикса prefix. func HasPrefix(s, prefix string) bool </code>
Если комментарию к документации необходимо объяснить несколько результатов, именование этих результатов может сделать комментарий более понятным, даже если имена не используются в теле функции. Например:
<code>package io
// Copy копирует данные из src в dst до тех пор, пока не будет достигнут конец файла
// на src или не произойдёт ошибка. Возвращает общее количество записанных байт
// и первую ошибку, возникшую при копировании, если таковая имеется.
//
// Успешное выполнение Copy возвращает err == nil, а не err == EOF.
// Поскольку Copy определена как чтение из src до EOF, она не рассматривает
// EOF от Read как ошибку для отчета.
func Copy(dst Writer, src Reader) (n int64, err error) {
...
}
</code>
Напротив, когда результаты не требуют именования в комментарии к документации,
они обычно также опускаются в коде, как в примере Quote выше,
чтобы избежать лишнего загромождения.
Эти правила применимы как к обычным функциям, так и к методам. Для методов использование одного и того же имени получателя позволяет избежать лишних вариаций при перечислении всех методов типа:
<code>$ go doc bytes.Buffer
package bytes // import "bytes"
type Buffer struct {
// Has unexported fields.
}
Буфер — это буфер переменного размера байтов с методами Read и Write.
Нулевое значение для Buffer — это пустой буфер, готовый к использованию.
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)
...
</code>
Этот пример также демонстрирует, что функции уровня пакета, возвращающие тип T или указатель *T,
возможно, с дополнительным результатом ошибки,
показываются рядом с типом T и его методами,
при предположении, что они являются конструкторами типа T.
По умолчанию программисты могут предполагать, что функция уровня пакета безопасна для вызова из нескольких горутин; это обстоятельство не обязательно указывать явно.
С другой стороны, как указано в предыдущем разделе, использование экземпляра типа каким-либо образом, включая вызов метода, обычно предполагается ограниченным для одной горутины за раз. Если методы, безопасные для параллельного использования, не документированы в комментариях к типу, они должны быть документированы в комментариях к каждому методу отдельно. Например:
<code>package sql
// Close возвращает соединение в пул соединений.
// Все операции после вызова Close будут возвращать ErrConnDone.
// Close можно безопасно вызывать параллельно с другими операциями и будет
// блокироваться до завершения всех других операций. Может быть полезно сначала
// отменить контекст, а затем напрямую вызвать Close.
func (c *Conn) Close() error {
...
}
</code>
Обратите внимание, что комментарии к функциям и методам сосредоточены на том, что операция возвращает или делает, подробно описывая, что нужно знать вызывающей стороне. Особые случаи могут быть особенно важны для документирования. Например:
<code>package math
// Sqrt возвращает квадратный корень из x.
//
// Специальные случаи:
//
// Sqrt(+Inf) = +Inf
// Sqrt(±0) = ±0
// Sqrt(x < 0) = NaN
// Sqrt(NaN) = NaN
func Sqrt(x float64) float64 {
...
}
</code>
Комментарии к документации не должны объяснять внутренние детали, такие как алгоритм, используемый в текущей реализации. Лучше оставить такие детали в комментариях внутри тела функции. Может быть уместно указать асимптотические временные или пространственные границы, если эта деталь особенно важна для вызывающих сторон. Например:
<code>package sort
// Sort сортирует данные в порядке возрастания, определяемом методом Less.
// Он выполняет один вызов data.Len для определения n и O(n*log(n)) вызовов
// data.Less и data.Swap. Сортировка не гарантирует устойчивость.
func Sort(data Interface) {
...
}
</code>
Поскольку в этом комментарии к документации не упоминается, какой алгоритм сортировки используется, легче изменить реализацию в будущем, чтобы использовать другой алгоритм.
Константы
Синтаксис объявления Go позволяет группировать объявления, при этом один комментарий к документации может представлять группу связанных констант, а отдельные константы документируются только короткими комментариями в конце строки. Например:
<code>package scanner // import "text/scanner" // Результат Scan — это один из этих токенов или символ Юникода. const ( EOF = -(iota + 1) Ident Int Float Char ... ) </code>
Иногда группа не нуждается в комментариях к документации вообще. Например:
<code>package unicode // import "unicode" const ( MaxRune = '\U0010FFFF' // максимальный допустимый код Юникода. ReplacementChar = '\uFFFD' // представляет недопустимые кодовые точки. MaxASCII = '\u007F' // максимальное значение ASCII. MaxLatin1 = '\u00FF' // максимальное значение Latin-1. ) </code>
С другой стороны, несгруппированные константы, как правило, требуют полного комментария к документации, начинающегося с полного предложения. Например:
<code>package unicode // Version — это редакция Unicode, из которой взяты таблицы. const Version = "13.0.0" </code>
Типизированные константы отображаются рядом с объявлением их типа, и в результате часто опускают комментарии к группе const в пользу комментария к типу. Например:
<code>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 ... ) </code>
(См. pkg.go.dev/regexp/syntax#Op для HTML-представления.)
Vars
Соглашения для переменных совпадают с таковыми для констант. Например, ниже приведён набор сгруппированных переменных:
<code>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" ) </code>
И одна переменная:
<code>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,
...
}
</code>
Syntax
Комментарии документации Go написаны на простом синтаксисе, который поддерживает параграфы, заголовки, ссылки, списки и предварительно отформатированные блоки кода. Чтобы сделать комментарии лёгкими и читаемыми в исходных файлах, не поддерживается сложная функциональность, такая как изменение шрифта или использование HTML-кода. Поклонники Markdown могут рассматривать этот синтаксис как упрощённый подмножество Markdown.
Стандартный форматировщик gofmt перформатирует комментарии документации
для использования канонического форматирования для каждой из этих функций.
Gofmt стремится к читаемости и контролю пользователя над тем, как комментарии
пишутся в исходном коде, но при этом будет корректировать представление, чтобы
сделать семантическое значение конкретного комментария более ясным,
аналогично перформатированию 1+2 * 3 в обычном исходном коде в 1 + 2*3.
Gofmt удаляет начальные и конечные пустые строки в комментариях документации. Если все строки в комментарии документации начинаются с одной и той же последовательности пробелов и табуляций, gofmt удаляет этот префикс.
Paragraphs
Параграф — это последовательность непустых строк без отступа. Мы уже видели множество примеров параграфов.
Пара последовательных обратных апострофов (` U+0060) интерпретируется как левая кавычка Юникода (“ U+201C), а пара последовательных одиночных апострофов (' U+0027) интерпретируется как правая кавычка Юникода (” U+201D).
Gofmt сохраняет разрывы строк в тексте абзацев: он не переносит текст автоматически. Это позволяет использовать семантические разрывы строк, как было показано выше. Gofmt заменяет повторяющиеся пустые строки между абзацами одной пустой строкой. Gofmt также преобразует последовательные обратные апострофы или одинарные кавычки в их Юникодные интерпретации.
Примечания
Примечания — это специальные комментарии вида MARKER(uid): body. MARKER должен состоять как минимум из двух заглавных букв [A-Z], обозначающих тип примечания, а uid — как минимум из одного символа, обычно имя пользователя, который может предоставить дополнительную информацию. Символ :, следующий за uid, является необязательным.
Примечания собираются и отображаются в отдельном разделе на pkg.go.dev.
Например:
<code>// TODO(user1): refactor to use standard library context // BUG(user2): not cleaned up var ctx context.Context </code>
Устаревшие элементы
Абзацы, начинающиеся с Deprecated: , рассматриваются как уведомления об устаревших элементах. Некоторые инструменты будут выдавать предупреждения при использовании устаревших идентификаторов. pkg.go.dev по умолчанию скрывает их документацию.
Уведомления об устаревших элементах сопровождаются информацией об устаревании и рекомендацией по использованию альтернативного решения, если это применимо. Абзац с уведомлением не обязательно должен быть последним в комментарии документации.
Например:
<code>// 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() </code>
Заголовки
Заголовок — это строка, начинающаяся с символа решётки (U+0023), за которым следует пробел и текст заголовка. Чтобы строка распознавалась как заголовок, она должна быть без отступа и отделяться от соседних абзацев пустыми строками.
Например:
<code>// 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 </code>
С другой стороны:
<code>// #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. </code>
Синтаксис # был добавлен в Go 1.19. До Go 1.19 заголовки определялись неявно с помощью однострочных абзацев, удовлетворяющих определённым условиям, в частности отсутствию завершающей пунктуации.
Gofmt переформатирует строки, рассматривавшиеся как неявные заголовки
ранее версиями Go, чтобы использовать вместо них заголовки с #.
Если переформатирование неуместно — то есть, если строка не предназначалась для заголовка — самый простой способ сделать её абзацем — это добавить завершающую пунктуацию,
такую как точка или двоеточие, или же разбить её на две строки.
Ссылки
Блок непропустимых непустых строк без отступа определяет цели ссылок, если каждая строка имеет вид «[Текст]: URL». В других текстах в той же doc-комментарии «[Текст]» представляет собой ссылку на URL с заданным текстом — в HTML, <a href=“URL”>Текст</a>. Например:
<code>// 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/doc/articles/json_and_go.html package json </code>
Храня URL в отдельном разделе, этот формат минимально прерывает поток основного текста. Он также примерно соответствует Markdown формату ярлычков, без необязательного текста заголовка.
Если нет соответствующего объявления URL, тогда (за исключением doc-ссылок, описанных в следующем разделе) «[Текст]» не является гиперссылкой, и квадратные скобки сохраняются при отображении. Каждый doc-комментарий рассматривается независимо: определения целей ссылок в одном комментарии не влияют на другие комментарии.
Хотя блоки определения целей ссылок могут чередоваться с обычными абзацами,
gofmt перемещает все определения целей ссылок в конец doc-комментария,
до двух блоков: первый содержит все цели ссылок,
упомянутые в комментарии, а затем блок,
содержащий все цели не упомянутые в комментарии.
Отдельный блок делает неиспользуемые цели легко заметными
и их можно исправить (в случае, если ссылки или определения содержат опечатки)
или удалить (в случае, если определения больше не нужны).
Обычный текст, распознаваемый как URL, автоматически связывается в HTML-выводе.
Doc-ссылки
Doc-ссылки — это ссылки вида «[Имя1]» или «[Имя1.Имя2]» для обращения к экспортируемым идентификаторам в текущем пакете, или «[pkg]», «[pkg.Имя1]» или «[pkg.Имя1.Имя2]» для обращения к идентификаторам в других пакетах.
Например:
<code>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) {
...
}
</code>
Текст в скобках для ссылки на символ может включать необязательный ведущий звёздочку, что облегчает ссылку на указательные типы, такие как [*bytes.Buffer].
При ссылке на другие пакеты, «pkg» может быть либо полным путём импорта, либо предполагаемым именем пакета существующего импорта. Предполагаемое имя пакета — это либо идентификатор в переименованном импорте, либо имя, assumed by goimports. (Инструмент goimports вставляет переименования, когда это предположение неверно, поэтому это правило работает практически для всего кода на Go.) Например, если текущий пакет импортирует encoding/json, то “[json.Decoder]” может быть записан вместо “[encoding/json.Decoder]” для ссылки на документацию для Decoder из encoding/json. Если разные исходные файлы в пакете импортируют разные пакеты с одинаковыми именами, то сокращение становится неоднозначным и не может быть использовано.
Пакет “pkg” предполагается полным путём импорта только в том случае, если он начинается с доменного имени (элемент пути с точкой) или является одним из пакетов стандартной библиотеки (“[os]”, “[encoding/json]” и так далее).
Например, [os.File] и [example.com/sys.File] являются ссылками на документацию
(последняя будет broken link),
но [os/sys.File] — нет, потому что в стандартной библиотеке нет пакета os/sys.
Чтобы избежать проблем с картами, дженериками и массивами, ссылки в документации должны быть как предшествовать, так и следовать за знаками препинания, пробелами, табуляцией или началом или концом строки. Например, текст “map[ast.Expr]TypeAndValue” не содержит ссылки на документацию.
Списки
Список — это блок отступов или пустых строк (которые иначе были бы блоком кода, как описано в следующем разделе), в котором первая отступленная строка начинается с маркера маркированного или нумерованного списка.
Маркер маркированного списка — это звёздочка, плюс, тире или юникодная марка (*, +, -, •; U+002A, U+002B, U+002D, U+2022) за которой следует пробел или табуляция, а затем текст. В маркированном списке каждая строка, начинающаяся с маркера маркированного списка, начинает новый элемент списка.
Например:
<code>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 {
...
}
</code>
Маркер нумерованного списка — это десятичное число любой длины, за которым следует точка или правая скобка, затем пробел или табуляция, а затем текст. В нумерованном списке каждая строка, начинающаяся с маркера нумерованного списка, начинает новый элемент списка. Номера элементов остаются без изменений, никогда не перенумеровываются.
Например:
<code>package path
// Clean возвращает кратчайшее имя пути, эквивалентное path,
// используя только лексический анализ. Она применяет следующие правила
// итеративно до тех пор, пока не будет невозможно дальнейшей обработки:
//
// 1. Заменяет несколько слешей на один слеш.
// 2. Удаляет каждый элемент пути . (текущая директория).
// 3. Удаляет каждый внутренний элемент .. (родительская директория)
// вместе с предшествующим ему элементом, который не является ..
// 4. Удаляет элементы .., которые начинаются с корневого пути:
// то есть заменяет "/.." на "/" в начале пути.
//
// Возвращаемый путь заканчивается слешем только в случае корневого "/"
//
// Если результатом этой процедуры является пустая строка, Clean
// возвращает строку ".".
//
// См. также Роб Пайк, “[Lexical File Names in Plan 9].”
//
// [Lexical File Names in Plan 9]: https://9p.io/sys/doc/lexnames.html
func Clean(path string) string {
...
}
</code>
Элементы списка содержат только абзацы, а не блоки кода или вложенные списки. Это избавляет от любой сложности, связанной с подсчётом пробелов, а также вопросов о том, сколько пробелов соответствует табуляции при непоследовательной отступации.
Gofmt переформатирует маркированные списки, используя дефис как маркер, два пробела отступа перед дефисом и четыре пробела отступа для строк продолжения.
Gofmt переформатирует нумерованные списки, используя один пробел перед числом, точку после числа и снова четыре пробела отступа для строк продолжения.
Gofmt сохраняет, но не требует пустую строку между списком и предыдущим абзацем. Он вставляет пустую строку между списком и последующим абзацем или заголовком.
Блоки кода
Блок кода — это группа отступов или пустых строк, не начинающихся с маркера маркированного или нумерованного списка. Он отображается как предварительно отформатированный текст (элемент <pre> в HTML).
Блоки кода часто содержат код на Go. Например:
<code>package sort
// Search использует бинарный поиск...
//
// В качестве более веселого примера, эта программа угадывает ваше число:
//
// 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 {
...
}
</code>
Конечно, блоки кода также часто содержат предварительно отформатированный текст, кроме кода. Например:
<code>package path
// Match сообщает, соответствует ли имя шаблону оболочки.
// Синтаксис шаблона:
//
// pattern:
// { term }
// term:
// '*' соответствует любой последовательности символов, кроме '/'
// '?' соответствует одному символу, кроме '/'
// '[' [ '^' ] { character-range } ']'
// класс символов (должен быть непустым)
// c соответствует символу c (c != '*', '?', '\\', '[')
// '\\' c соответствует символу c
//
// character-range:
// c соответствует символу c (c != '\\', '-', ']')
// '\\' c соответствует символу c
// lo '-' hi соответствует символу c, где lo <= c <= hi
//
// Match требует, чтобы шаблон соответствовал всему имени, а не только подстроке.
// Единственная возможная возвращаемая ошибка — [ErrBadPattern], когда шаблон
// имеет неправильный формат.
func Match(pattern, name string) (matched bool, err error) {
...
}
</code>
Gofmt выравнивает все строки в блоке кода по одному символу табуляции, заменяя любое другое выравнивание, которое имеют непустые строки, общее для них. Gofmt также вставляет пустую строку до и после каждого блока кода, четко выделяя его от окружающего текста абзаца.
Директивы
Комментарии-директивы, такие как //go:generate, не считаются частью документационного комментария и исключаются из отображаемой документации. Gofmt перемещает комментарии-директивы в конец документационного комментария, предваряя их пустой строкой. Например:
<code>package regexp // An Op is a single regular expression operator. // //go:generate stringer -type Op -trimprefix Op type Op uint8 </code>
Комментарий-директива — это строка, начинающаяся с регулярного выражения
//(line |extern |export |[a-z0-9]+:[a-z0-9]).
Инструменты могут определять собственные комментарии-директивы, используя форму
//toolname:directive arguments.
Директивы инструментов соответствуют регулярному выражению
//([a-z0-9]+):([a-z0-9]\PZ*)($|\pZ+)(.*), где первая группа — это имя инструмента, а вторая группа — имя директивы.
Необязательные аргументы разделяются от имени директивы одним или несколькими пробельными символами Юникода.
Каждый инструмент может определять собин синтаксис аргументов, но общепринятая конвенция — это последовательность аргументов, разделенных пробелами, где аргумент может быть словом без кавычек, или двойными или обратными кавычками в стиле Go.
Имя инструмента go зарезервировано для использования в составе инструментария Go.
Функция go/ast.ParseDirective и связанные с ней типы разбирают синтаксис директив инструментов.
Частые ошибки и подводные камни
Правило, согласно которому любой фрагмент отступов или пустых строк в документационном комментарии отображается как блок кода, датируется самыми ранними днями развития Go. К сожалению, отсутствие поддержки документационных комментариев в gofmt привело к тому, что множество существующих комментариев используют отступы, не имея намерения создать блок кода.
Например, этот неотступленный список всегда интерпретировался godoc как трехстрочный абзац, за которым следует однострочный блок кода:
<code>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 {
...
}
</code>
Это всегда отображалось в go doc как:
<code>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. </code>
Аналогично, команда в этом комментарии представляет собой однострочный абзац, за которым следует однострочный блок кода:
<code>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(`...`) </code>
Это отображается в go doc следующим образом:
<code>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 </code>
А этот комментарий представляет собой абзац из двух строк (вторая строка — «{»), за которым следует шестистрочный отступ блока кода и однострочный абзац («}»).
<code>// On the wire, the JSON will look something like this:
// {
// "kind":"MyAPIObject",
// "apiVersion":"v1",
// "myPlugin": {
// "kind":"PluginA",
// "aOption":"foo",
// },
// }
</code>
А это отображается в go doc следующим образом:
<code>On the wire, the JSON will look something like this: {
"kind":"MyAPIObject",
"apiVersion":"v1",
"myPlugin": {
"kind":"PluginA",
"aOption":"foo",
},
}
</code>
Еще одна распространенная ошибка — это неотступная определение функции Go или блок инструкции, аналогично заключённые в «{» и «}».
Введение переформатирования комментариев документации в gofmt Go 1.19 делает такие ошибки более заметными, добавляя пустые строки вокруг блоков кода.
Анализ 2022 года показал, что только 3% комментариев документации в публичных Go модулях вообще были переформатированы с помощью чернового gofmt Go 1.19. Ограничиваясь этими комментариями, около 87% переформатированных элементов gofmt сохранили структуру, которую человек мог бы вывести при чтении комментария; около 6% были запутаны этими видами неотступных списков, неотступных многострочных команд оболочки и неотступных блоков кода, ограниченных фигурными скобками.
На основе этого анализа, gofmt Go 1.19 применяет несколько эвристик для объединения неотступных строк в соседний отступный список или блок кода. С этими корректировками, gofmt Go 1.19 переформатирует вышеуказанные примеры следующим образом:
<code>// 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",
// },
// }
</code>
Такое переформатирование делает смысл более ясным, а также обеспечивает правильное отображение комментариев документации в более ранних версиях Go. Если эвристика когда-либо принимает неправильное решение, это можно переопределить, вставив пустую строку для четкого разделения текста абзаца от не-абзацного текста.
Даже с этими эвристиками, другие существующие комментарии потребуют ручной корректировки для исправления их отображения. Наиболее распространенная ошибка — это отступ обернутой неотступной строки текста. Например:
<code>// TODO Пересмотреть этот дизайн. Возможно, имеет смысл пройтись по этим узлам // только один раз. // Согласно документу: // "Фактор выравнивания (в байтах), который используется для выравнивания необработанных данных секций в // файле образа. Значение должно быть степенью двойки между 512 и 64 К, включительно." </code>
В обоих этих случаях последняя строка имеет отступ, что делает её блоком кода. Исправление заключается в удалении отступа у строк.
Ещё одна распространённая ошибка — это отсутствие отступа у обёрнутой отступной строки списка или блока кода. Например:
<code>// Использование этой модели ошибок включает: // // - Частичные ошибки. Если сервису нужно вернуть частичные ошибки клиенту, // он может встроить `Status` в обычный ответ, чтобы указать частичные // ошибки. // // - Ошибки рабочего процесса. Типичный рабочий процесс имеет несколько шагов. Каждый шаг // может иметь сообщение `Status` для отчёта об ошибках. </code>
Исправление заключается в добавлении отступа к обёрнутым строкам.
Комментарии документации Go не поддерживают вложенные списки, поэтому gofmt перформатирует
<code>// Вот список: // // - Элемент 1. // * Подэлемент 1. // * Подэлемент 2. // - Элемент 2. // - Элемент 3. </code>
в
<code>// Вот список: // // - Элемент 1. // - Подэлемент 1. // - Подэлемент 2. // - Элемент 2. // - Элемент 3. </code>
Переписывание текста с целью избежать вложенных списков обычно улучшает документацию и является лучшим решением. Другой потенциальный обходной путь — это смешивание маркеров списков, поскольку маркеры точек не создают элементы списка в нумерованном списке, и наоборот. Например:
<code>// Вот список: // // 1. Элемент 1. // // - Подэлемент 1. // // - Подэлемент 2. // // 2. Элемент 2. // // 3. Элемент 3. </code>