Поиск по сайту:

Как писать комментарии в Go


Введение

Практически все языки программирования имеют синтаксис для добавления комментариев в код, и Go не исключение. Комментарии — это строки в программе, которые на человеческом языке объясняют, как работает код или почему он написан таким, какой он есть. Они игнорируются компилятором, но не внимательными программистами. Комментарии добавляют бесценный контекст, который помогает вашим соавторам — и вам в будущем — избегать ловушек и писать более удобный для сопровождения код.

Обычные комментарии внутри любого пакета объясняют, почему этот код делает то, что делает. Это примечания и предупреждения для разработчиков пакета. Комментарии к документам обобщают, что делает каждый компонент пакета и как он работает, а также предоставляют примеры кода и использования команд. Это официальная документация пакета для его пользователей.

В этой статье мы рассмотрим некоторые реальные комментарии из нескольких пакетов Go, чтобы проиллюстрировать не только то, как комментарии выглядят в Go, но и то, что они должны нести.

Обычные комментарии

Комментарий в Go начинается с двух косых черт (//), за которыми следует один пробел (не обязательный, но идиоматический), а затем комментарий. Он может появиться чуть выше или справа от кода, к которому он относится. Когда выше, он имеет отступ для выравнивания с кодом.

Эта программа Hello World содержит один комментарий в отдельной строке:

package main

import "fmt"

func main() {
	// Say hi via the console
	fmt.Println("Hello, World!")
}

Примечание: если вы добавляете комментарий, который не соответствует коду, Gopher (так называют энтузиастов Go), вы должны постоянно форматировать свой код Go по мере его написания — и, конечно же, перед передачей его в систему контроля версий. Вы можете запустить gofmt вручную (gofmt -w hello.go), но удобнее настроить текстовый редактор или IDE так, чтобы он запускался каждый раз при сохранении файлов.

Поскольку этот комментарий очень короткий, вместо этого он может отображаться как встроенный комментарий справа от кода:

. . .
	fmt.Println("Hello, World!") // Say hi via the console
. . .

Большинство комментариев появляются на отдельной строке, если только они не очень краткие, как этот.

Более длинные комментарии занимают несколько строк. Go поддерживает блочные комментарии в стиле C с использованием тегов /* и */ для открытия и закрытия очень длинных комментариев, но они используются только в специальных случаи. (Подробнее об этом позже.) Обычные многострочные комментарии начинаются с // , а не с тегов блочных комментариев.

Вот некоторый код со многими комментариями, каждый из которых имеет правильный отступ. Выделен один многострочный комментарий:

package main

import "fmt"

const favColor string = "blue" // Could have chosen any color

func main() {
	var guess string
	// Create an input loop
	for {
		// Ask the user to guess my favorite color
		fmt.Println("Guess my favorite color:")

                // Try to read a line of input from the user.
                // Print out an error and exit, if there is one.
		if _, err := fmt.Scanln(&guess); err != nil {
			fmt.Printf("%s\n", err)
			return
		}

		// Did they guess the correct color?
		if favColor == guess {
			// They guessed it!
			fmt.Printf("%q is my favorite color!\n", favColor)
			return
		}
		// Wrong! Have them guess again.
		fmt.Printf("Sorry, %q is not my favorite color. Guess again.\n", guess)
	}
}

Большинство из этих комментариев на самом деле беспорядок. Такая маленькая и простая программа не должна содержать столько комментариев, и большинство из них говорит что-то, что очевидно из самого кода. Вы можете доверить другим программистам Go понимание основ синтаксиса Go, потока управления, типов данных и так далее. Вам не нужно писать комментарий, объявляющий, что код собирается выполнить итерацию по срезу или умножить два числа с плавающей запятой.

Однако один из этих комментариев полезен.

Хорошие комментарии объясняют, почему

Самые полезные комментарии в любой программе — те, которые объясняют не то, что код делает или как он это делает, а почему он это делает. Иногда нет почему, и даже это может быть полезно указать, как это делает встроенный комментарий:

const favColor string = "blue" // Could have chosen any color

Этот комментарий говорит о том, чего код не может сказать: значение \blue было выбрано программистом произвольно. Другими словами, // Вы можете изменить это.

Однако в большинстве кодов есть почему. Вот функция в пакете net/http из стандартной библиотеки Go, которая содержит два очень полезных комментария:

. . .
// refererForURL returns a referer without any authentication info or
// an empty string if lastReq scheme is https and newReq scheme is http.
func refererForURL(lastReq, newReq *url.URL) string {
	// https://tools.ietf.org/html/rfc7231#section-5.5.2
	//   "Clients SHOULD NOT include a Referer header field in a
	//    (non-secure) HTTP request if the referring page was
	//    transferred with a secure protocol."
	if lastReq.Scheme == "https" && newReq.Scheme == "http" {
		return ""
	}
	referer := lastReq.String()
	if lastReq.User != nil {
		// This is not very efficient, but is the best we can
		// do without:
		// - introducing a new method on URL
		// - creating a race condition
		// - copying the URL struct manually, which would cause
		//   maintenance problems down the line
		auth := lastReq.User.String() + "@"
		referer = strings.Replace(referer, auth, "", 1)
	}
	return referer
}
. . .

Первый выделенный комментарий предупреждает сопровождающих не изменять приведенный ниже код, поскольку он намеренно написан в соответствии с RFC (официальной спецификацией) для протокола HTTP. Второй выделенный комментарий признает, что приведенный ниже код не идеален, намекает на то, как сопровождающий может попытаться его улучшить, и предупреждает об опасности этого.

Такие комментарии незаменимы. Они не дают мейнтейнерам непреднамеренно создавать ошибки и другие проблемы, но также могут предложить им реализовать новые идеи — с осторожностью.

Комментарий над объявлением func тоже полезен, но по-другому. Давайте рассмотрим такой комментарий дальше.

Комментарии к документу

Комментарии, которые появляются непосредственно над объявлениями верхнего уровня (без отступа), такими как package, func, const, var и type называются комментариями документа. Они названы так потому, что представляют официальную документацию для пакета и всех его экспортируемых имен.

Примечание. В Go экспортированный означает то же самое, что и общедоступный в некоторых языках: экспортируемый компонент — это компонент, который другие пакеты могут использовать при импорте вашего пакета. Чтобы экспортировать любое имя верхнего уровня в вашем пакете, все, что вам нужно сделать, это написать его с большой буквы.

Комментарии к документу объясняют, что и как

В отличие от обычных комментариев, которые мы только что видели, док-комментарии обычно объясняют, что делает код или как он это делает. Это потому, что они предназначены не для сопровождающих пакета, а для его пользователей, которые обычно не хотят читать или вносить свой вклад в код.

Пользователи обычно читают ваши комментарии к документам в одном из трех мест:

  1. В своем локальном терминале, запустив go doc для отдельного исходного файла или каталога.
  2. На pkg.go.dev, официальном сайте документации по любому общедоступному пакету Go.
  3. На частном веб-сервере, размещенном вашей командой с помощью инструмента godoc. Этот инструмент позволяет вашей команде создать собственный справочный портал для частных пакетов Go.

При разработке пакетов Go вы должны написать комментарий для каждого экспортируемого имени. (И godo, клиентская библиотека Go для API DigitalOcean:

// Client manages communication with DigitalOcean V2 API.
type Client struct {

Такой простой комментарий к документации может показаться ненужным, но помните, что он будет отображаться вместе со всеми другими вашими комментариями к документации, чтобы всесторонне документировать каждый используемый компонент пакета.

Вот более длинный комментарий к документу из пакета:

// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
. . .
}

В комментариях к функциям следует четко указывать ожидаемый формат параметров (когда это не очевидно) и формат данных, возвращаемых функцией. Они также могут резюмировать, как работает функция.

Сравните комментарий документа для функции Do с этим комментарием внутри функции:

	// Ensure the response body is fully read and closed
	// before we reconnect, so that we reuse the same TCPConnection.
	// Close the previous response's body. But read at least some of
	// the body so if it's small the underlying TCP connection will be
	// re-used. No need to check for errors: if it fails, the Transport
	// won't reuse it anyway.

Это похоже на комментарии, которые мы видели в пакете net/http. Сопровождающий, читающий код под этим комментарием, может задаться вопросом: «Почему это не проверяет ошибки?», а затем добавляет проверку ошибок. Но комментарий объясняет, почему им не нужно этого делать. уровень что или как, как и комментарии к документам.

Комментарии к документам самого высокого уровня — это комментарии к пакетам. Каждый пакет должен содержать только один комментарий к пакету, который дает общий обзор того, что представляет собой пакет и как его использовать, включая примеры кода и/или команд. Комментарий к пакету может появиться в любом исходном файле — и только в этом файле — над объявлением package . Часто комментарий к пакету появляется в отдельном файле с именем doc.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.
. . .
*/
package main

А как насчет формата комментариев к документам? Какую структуру они могут (или должны) иметь?

Комментарии к документам имеют формат

Согласно старому сообщению в блоге создателей Go:

Godoc концептуально связан с Docstring Python и Javadoc Java, но его дизайн проще. Комментарии, читаемые godoc, не являются языковыми конструкциями (как в случае с Docstring) и не должны иметь собственного машиночитаемого синтаксиса (как в случае с Javadoc). Комментарии к Godoc — это просто хорошие комментарии, которые вы хотели бы прочитать, даже если бы Godoc не существовало.

Хотя комментарии к документам не имеют обязательного формата, они могут по желанию использовать формат, который является «упрощенным подмножеством Markdown», полностью описанным в документации Go. В ваших комментариях к документам вы будете писать в абзацах и списках, показывать пример кода или команды в блоки с отступом, ссылки на ссылки и т. д. Когда комментарии к документам хорошо структурированы в соответствии с этим форматом, они могут быть преобразованы в красивые веб-страницы.

Вот несколько комментариев, добавленных к расширенной программе Hello World greeting.go из How To Write Your First Program in Go:

// This is a doc comment for greeting.go.
//  - prompt user for name.
//   - wait for name
//    - print name.
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main

import (
	"fmt"
	"strings"
)

func main() {
	// This is not a doc comment. Gofmt will NOT format it.
	//  - prompt user for name
	//   - wait for name
	//    - print name
	// This is not a "second paragraph" because this is not a doc comment.
	// It's just more lines to this non-doc comment.
	fmt.Println("Please enter your name.")
	var name string
	fmt.Scanln(&name)
	name = strings.TrimSpace(name)
	fmt.Printf("Hi, %s! I'm Go!", name)
}

Комментарий над package main является документальным комментарием. Он пытается использовать понятия абзацев и списков формата комментариев документа, но это не совсем правильно. Инструмент gofmt преобразует его в формат. Запуск gofmt Greeting.go напечатает следующее:

// This is a doc comment for greeting.go.
//   - prompt user for name.
//   - wait for name.
//   - print name.
//
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main

import (
	"fmt"
	"strings"
)

func main() {
	// This is not a doc comment. `gofmt` will NOT format it.
	//  - prompt user for name
	//   - wait for name
	//    - print name
	// This is not a "second paragraph" because this is not a doc comment.
	// It's just more lines to this non-doc comment.
	fmt.Println("Please enter your name.")
	var name string
	fmt.Scanln(&name)
	name = strings.TrimSpace(name)
	fmt.Printf("Hi, %s! I'm Go!", name)
}

Заметить, что:

  1. Элементы, перечисленные в первом абзаце комментария документа, теперь выровнены.
  2. Первый и второй абзацы теперь разделены пустой строкой.
  3. Комментарий внутри main() не был отформатирован, так как gofmt распознал, что это не комментарий документа. (Но, как упоминалось ранее, gofmt выравнивает все комментарии по тому же отступу, что и код.)

Запуск go doc Greeting.go отформатирует и напечатает комментарий к документу, но не тот, что находится внутри main():

This is a doc comment for greeting.go.
  - prompt user for name.
  - wait for name.
  - print name.

This is the second paragraph of this doc comment. `gofmt` (and `go doc`) will
insert a blank line before it.

Если вы будете использовать этот формат комментариев последовательно и надлежащим образом, пользователи вашего пакета будут благодарны вам за удобную для чтения документацию.

Прочтите официальную справочную страницу комментариев к документам, чтобы узнать все о том, как их правильно писать.

Быстрое отключение кода

Вы когда-нибудь писали новый код, который замедлял работу вашего приложения или, что еще хуже, ломал все? Сейчас самое время использовать теги /* и */ в стиле C. Вы можете быстро отключить проблемный код, поместив один /* перед ним и один */ после него. Оберните эти теги вокруг проблемного кода, чтобы превратить его в блочный комментарий. Затем, когда вы исправите любую проблему, которую она вызывала, вы можете снова включить код, удалив два тега.

. . .
func main() {
    x := initializeStuff()
    /* This code is causing problems, so we're going to comment it out for now
    someProblematicCode(x)
    */
    fmt.Println("This code is still running!")
}

Для более длинных фрагментов кода использование этих тегов намного удобнее, чем добавление // в начало каждой строки проблемного кода. По соглашению используйте // для обычных комментариев и комментариев документов, которые будут жить в вашем коде неопределенное время. Используйте теги /* и */ только временно во время тестирования (или для комментариев к пакету, как упоминалось ранее). Не оставляйте фрагменты закомментированного кода в своей программе на длительное время.

Заключение

Написав выразительные комментарии во всех своих программах на Go, вы:

  1. Не позволяйте вашим соавторам ломать вещи.
  2. Помощь самому себе в будущем, который иногда забывает, почему код изначально был написан таким, какой он есть.
  3. Предоставьте пользователям вашего пакета справку, которую они смогут прочитать, не углубляясь в ваш код.