Go 문서 주석

Go 문서 주석 (Go Doc Comments)

문서 주석(doc comment)이란, 최상위 수준의 패키지·const·func·type·var 선언 바로 앞에 오면서 그 사이에 빈 줄이 없는 주석을 말해요. 모든 내보낸(exported, 대문자로 시작하는) 이름에는 문서 주석이 있어야 합니다.

go/docgo/doc/comment 패키지는 Go 소스 코드에서 문서를 추출하는 기능을 제공하고, 다양한 도구가 이 기능을 활용합니다.

  • go doc 명령은 주어진 패키지나 심볼의 문서 주석을 찾아 출력합니다. (심볼은 최상위 수준의 const, func, type 또는 var예요.)
  • 웹 서버 pkg.go.dev는 공개 Go 패키지의 문서를 보여줍니다(라이선스가 허용하는 경우).
  • 그 사이트를 서빙하는 프로그램은 golang.org/x/pkgsite/cmd/pkgsite로, 사설 모듈이나 인터넷 연결 없이 문서를 보려고 로컬에서 실행할 수도 있어요.
  • 언어 서버 gopls는 IDE에서 Go 소스 파일을 편집할 때 문서를 제공합니다.

이 페이지의 나머지는 Go 문서 주석을 어떻게 작성하는지 설명합니다.

출처: Go 공식 문서

패키지 (Packages)

모든 패키지는 그 패키지를 소개하는 패키지 주석(package comment) 을 가져야 해요. 패키지 주석은 패키지 전체에 관련된 정보를 제공하고, 일반적으로 패키지에 대한 기대치를 세워주죠. 특히 큰 패키지에서는 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 "로 시작해야 합니다.

여러 파일로 이루어진 패키지라면 패키지 주석은 소스 파일 하나에만 있어야 해요. 여러 파일에 패키지 주석이 있으면 그것들이 이어붙어져 패키지 전체를 위한 커다란 주석 하나가 됩니다.

커맨드 (Commands)

커맨드의 패키지 주석도 비슷하지만, 패키지 안의 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

주석의 시작 부분은 시맨틱 라인피드(semantic linefeed) 로 작성됐어요. 각 문장이나 긴 구를 한 줄에 하나씩 두는 방식이죠. 코드와 주석이 진화하면서 diff를 더 읽기 쉽게 만들어 줍니다. 뒤의 단락들은 이 관례를 따르지 않고 손으로 줄바꿈했습니다. 코드 베이스에 맞는 방식이면 뭐든 좋아요. 어느 쪽이든 go doc과 pkgsite는 문서 주석 텍스트를 출력할 때 다시 감싸(rewrap) 줍니다. 예를 들어:

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

들여쓰기된 줄은 전서체 텍스트(preformatted text) 로 취급됩니다. 다시 감싸지 않고, HTML과 마크다운 표시에서는 코드 글꼴로 출력되죠. (Syntax 섹션에서 자세히 다룹니다.)

타입 (Types)

타입의 문서 주석은 그 타입의 각 인스턴스가 무엇을 나타내거나 제공하는지 설명해야 해요. API가 단순하면 문서 주석도 아주 짧을 수 있습니다. 예를 들어:

package zip

// A Reader serves content from a ZIP archive.
type Reader struct {
    ...
}

기본적으로 프로그래머는 타입이 한 번에 하나의 고루틴에서만 안전하게 사용된다고 기대해야 합니다. 만약 타입이 더 강한 보장을 제공한다면 문서 주석에 그걸 명시해야 해요. 예를 들어:

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 타입은 제로 값(zero value)이 유용한 의미를 갖도록 하는 것도 목표로 해야 해요. 그 의미가 명확하지 않다면 문서화돼야 합니다. 예를 들어:

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 {
    ...
}

내보낸 필드가 있는 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
$

함수 (Funcs)

함수의 문서 주석은 그 함수가 무엇을 반환하는지, 또는 부작용을 위해 호출되는 함수라면 무엇을 하는지 설명해야 해요. 이름 있는 파라미터와 결과는 백틱 같은 특별한 문법 없이 주석에서 직접 언급할 수 있습니다. (이 관례의 결과로 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를 (어쩌면 추가 error 결과와 함께) 반환하는 최상위 수준 함수가, T의 생성자라고 가정되어 T와 그 메서드들 옆에 함께 표시된다는 걸 보여줍니다.

기본적으로 프로그래머는 최상위 수준 함수가 여러 고루틴에서 호출해도 안전하다고 가정할 수 있어요. 이 사실을 명시적으로 밝힐 필요는 없습니다.

반면, 이전 섹션에서 언급했듯이 어떤 방식으로든(메서드 호출 포함) 타입의 인스턴스를 사용하는 건 보통 한 번에 하나의 고루틴으로 제한된다고 가정합니다. 동시 사용에 안전한 메서드가 타입의 문서 주석에 문서화되어 있지 않다면, 메서드별 주석에 문서화해야 해요. 예를 들어:

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 {
    ...
}

함수·메서드 문서 주석은 호출자가 알아야 할 것에 초점을 맞춰, 그 연산이 무엇을 반환하거나 하는지 상세히 다룹니다. 특수한 경우(special case)를 문서화하는 건 특히 중요할 수 있어요. 예를 들어:

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) {
    ...
}

이 문서 주석은 어떤 정렬 알고리즘을 쓰는지 언급하지 않으므로, 나중에 구현을 다른 알고리즘으로 바꾸기 쉽습니다.

상수 (Consts)

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"

타입이 있는 상수(typed constant)는 그 타입의 선언 옆에 표시되므로, 상수 그룹 문서 주석 대신 타입의 문서 주석을 따르는 경우가 많아요. 예를 들어:

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를 참고하세요.)

변수 (Vars)

변수의 관례는 상수와 동일합니다. 예를 들어, 다음은 그룹화된 변수 세트예요:

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,
    ...
}

문법 (Syntax)

Go 문서 주석은 문단, 제목, 링크, 목록, 전서식 코드 블록을 지원하는 단순한 문법으로 작성됩니다. 주석을 소스 파일에서 가볍고 읽기 쉽게 유지하기 위해, 글꼴 변경이나 원시 HTML 같은 복잡한 기능은 지원하지 않아요. 마크다운에 익숙한 분이라면 이 문법을 마크다운의 단순화된 부분 집합으로 볼 수 있습니다.

표준 포매터 gofmt는 문서 주석을 이러한 각 기능에 대해 표준 형식으로 다시 포매팅합니다. gofmt는 소스 코드에서 주석을 작성하는 방식의 가독성과 사용자 자유를 목표로 하지만, 특정 주석의 의미를 더 명확하게 하기 위해 표시를 조정하곤 해요. 이는 일반 소스 코드에서 1+2 * 31 + 2*3으로 다시 포매팅하는 것과 비슷합니다.

gofmt는 문서 주석의 앞뒤 빈 줄을 제거합니다. 문서 주석의 모든 줄이 같은 공백과 탭 시퀀스로 시작하면 gofmt가 그 접두사를 제거해요.

문단 (Paragraphs)

문단은 들여쓰기되지 않은 비어 있지 않은 줄의 범위입니다. 우리는 이미 문단의 많은 예를 봤어요.

연속된 백틱 한 쌍(` U+0060)은 유니코드 왼쪽 따옴표(“ U+201C)로 해석되고, 연속된 작은따옴표 한 쌍(' U+0027)은 유니코드 오른쪽 따옴표(” U+201D)로 해석됩니다.

gofmt는 문단 텍스트의 줄바꿈을 보존합니다. 텍스트를 다시 감싸지 않아요. 덕분에 앞서 본 것처럼 시맨틱 라인피드를 쓸 수 있습니다. gofmt는 문단 사이의 중복 빈 줄을 하나로 줄이고, 연속된 백틱이나 작은따옴표를 유니코드 해석으로 다시 포매팅합니다.

참고(Notes)

참고(Note)는 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

폐기(Deprecations)

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()

제목 (Headings)

제목은 숫자 기호(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에서는 <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://golang.org/doc/articles/json_and_go.html
package json

URL을 별도 섹션에 둠으로써, 이 형식은 실제 텍스트의 흐름을 최소한으로만 끊어요. 또한 선택적 타이틀 텍스트가 없다는 점만 빼면 마크다운의 단축 참조 링크 형식과 거의 일치합니다.

상응하는 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"는 전체 import 경로이거나, 기존 import의 가정된 패키지 이름일 수 있습니다. 가정된 패키지 이름은 이름을 바꾼 import의 식별자이거나, goimports가 가정하는 이름이에요. (Goimports는 그 가정이 옳지 않을 때 바꿔치기를 삽입하므로, 이 규칙은 본질적으로 모든 Go 코드에 작동합니다.) 예를 들어 현재 패키지가 encoding/json을 import 한다면, [json.Decoder][encoding/json.Decoder] 대신 써서 encoding/json의 Decoder 문서로 연결할 수 있어요. 다른 소스 파일이 같은 이름으로 다른 패키지를 import 한다면 그 약칭은 모호해서 쓸 수 없습니다.

"pkg"는 도메인 이름(점이 있는 경로 요소)으로 시작하거나 표준 라이브러리의 패키지([os], [encoding/json] 등) 중 하나일 때만 전체 import 경로로 간주됩니다. 예를 들어 [os.File][example.com/sys.File]은 문서 링크입니다(후자는 깨진 링크가 되겠죠). 하지만 [os/sys.File]은 문서 링크가 아니에요. 표준 라이브러리에 os/sys 패키지가 없으니까요.

맵, 제네릭, 배열 타입과의 문제를 피하기 위해, 문서 링크는 앞뒤가 모두 구두점, 공백, 탭, 또는 줄의 시작·끝이어야 합니다. 예를 들어 map[ast.Expr]TypeAndValue 텍스트는 문서 링크를 포함하지 않아요.

목록 (Lists)

목록은 (다음 섹션에서 설명하듯 코드 블록이 되었을) 들여쓰기되거나 빈 줄의 범위인데, 그 첫 들여쓰기 줄이 글머리 목록 마커번호 목록 마커로 시작하는 경우입니다.

글머리 목록 마커는 별표, 더하기, 대시, 유니코드 글머리(*, +, -, •; 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는 목록과 앞 문단 사이의 빈 줄을 보존하지만 요구하지는 않습니다. 목록과 뒤따르는 문단 또는 제목 사이에는 빈 줄을 삽입해요.

코드 블록 (Code blocks)

코드 블록은 글머리 목록 마커나 번호 목록 마커로 시작하지 않는 들여쓰기되거나 빈 줄의 범위예요. 전서식 텍스트로 렌더링됩니다(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는 또 각 코드 블록 앞뒤에 빈 줄을 삽입해서 코드 블록을 주변 문단 텍스트와 명확히 구분해 줍니다.

지시어 (Directives)

//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 arguments 형식으로 자체 지시어 주석을 정의할 수 있어요. 도구 지시어는 정규식 //([a-z0-9]+):([a-z0-9]\PZ*)($|\pZ+)(.*)와 일치하는데, 첫 번째 그룹은 도구 이름, 두 번째 그룹은 지시어 이름입니다. 선택 인자는 하나 이상의 유니코드 공백 문자로 지시어 이름과 구분됩니다. 각 도구는 자체 인자 문법을 정의할 수 있지만, 흔한 관례는 공백으로 구분된 인자 시퀀스로, 인자는 단어 하나이거나 큰따옴표·백틱으로 묶인 Go 문자열일 수 있어요. 도구 이름 go는 Go 툴체인이 사용하도록 예약되어 있습니다. go/ast.ParseDirective 함수와 관련 타입들이 도구 지시어 문법을 파싱합니다.

흔한 실수와 함정 (Common mistakes and pitfalls)

문서 주석에서 들여쓰기되거나 빈 줄의 범위가 코드 블록으로 렌더링된다는 규칙은 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에 의해 다시 포매팅됐고, 그 주석들로 한정하면 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",
//      },
//  }

이 다시 포매팅은 의미를 더 명확하게 하고, 이전 Go 버전에서도 문서 주석이 올바르게 렌더링되게 해줘요. 휴리스틱이 나쁜 결정을 내리면, 빈 줄을 삽입해 문단 텍스트와 비문단 텍스트를 명확히 분리해서 덮어쓸 수 있습니다.

이 휴리스틱이 있어도 다른 기존 주석들은 렌더링을 고치기 위해 수동 조정이 필요해요. 가장 흔한 실수는 감싸진(wrapped) 들여쓰기되지 않은 텍스트 줄을 들여쓰는 것입니다. 예를 들어:

// 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.

더 알아보기 (Learn more)