Upstream golang/go has shifted several line numbers since citations were recorded. Updated 6 citations across 3 files: - documentation.md: server.go:55-57 → 59-62 (ErrWriteAfterFlush) - documentation.md: transport.go:79-80 → 72-73 (Transports should be reused) - structs.md: client.go:31-35 → 30-34 (A Client is an HTTP client) - structs.md: client.go:59-60 → 57-58 (type Client struct) - style.md: stream.go:280 → 292 (var _ Marshaler) - style.md: stream.go:280-281 → 292-293 (var _ Marshaler/Unmarshaler) Verified against golang/go HEAD (depth=1 clone).
16 KiB
Documentation Patterns in the Go Standard Library
Source: golang/go at commit 17bd5ab
1. Package Documentation (doc.go or Package Comment)
Pattern name: Package Doc Comment
Source citation: net/http/doc.go#L6, os/file.go#L5, log/slog/doc.go#L6
What it does: The first file in a package (by convention doc.go, or the main
source file) starts with a // Package xxx ... comment that explains the package's
purpose, key types, and typical usage patterns.
Why: This is the first thing users see in go doc <pkg> and on pkg.go.dev. It
sets context, teaches the mental model, and provides copy-paste examples.
Anti-pattern: No package comment; package comment that just restates the package name ("Package http provides http"); putting documentation in README instead of code.
Code examples from source:
// net/http/doc.go:6-12
/*
Package http provides HTTP client and server implementations.
[Get], [Head], [Post], and [PostForm] make HTTP (or HTTPS) requests:
resp, err := http.Get("http://example.com/")
...
*/
// os/file.go:5-43
// Package os provides a platform-independent interface to operating system
// functionality. The design is Unix-like, although the error handling is
// Go-like; failing calls return values of type error rather than error numbers.
// Often, more information is available within the error. For example,
// if a call that takes a file name fails, such as [Open] or [Stat], the error
// will include the failing file name when printed and will be of type
// [*PathError], which may be unpacked for more information.
// log/slog/doc.go:6-10
/*
Package slog provides structured logging,
in which log records include a message,
a severity level, and various other attributes
expressed as key-value pairs.
*/
2. Section Headers in Package Docs
Pattern name: # Heading in Doc Comments
Source citation: os/file.go#L37, net/http/doc.go (multiple sections)
What it does: Uses # Section Name within the package doc comment to organize
long documentation into navigable sections.
Why: Large packages need structure. Section headers render as links in pkg.go.dev and provide a scannable table of contents.
Anti-pattern: Wall-of-text package docs; using === or --- (not recognized);
too many sections (fragmenting simple docs).
Code example from source:
// os/file.go:37
// # Concurrency
//
// The methods of [File] correspond to file system operations. All are
// safe for concurrent use.
3. Type/Function Comment Convention
Pattern name: // TypeName verb... or // FuncName verb...
Source citation: net/http/server.go#L65 (Handler), bufio/scan.go#L14 (Scanner)
What it does: Every exported identifier's doc comment starts with the identifier name, followed by a verb phrase describing what it does or represents.
Why: go doc extracts the first sentence as a summary. Starting with the name
ensures it reads correctly in both isolation (summary lists) and full context.
This is enforced by convention and checked by linters.
Anti-pattern: Starting with "This function..." or "The Foo type..."; starting with articles ("A Handler is...") for functions (acceptable for types); omitting the comment entirely.
Code examples from source:
// net/http/server.go:65
// A Handler responds to an HTTP request.
// bufio/scan.go:14-17
// Scanner provides a convenient interface for reading data such as
// a file of newline-delimited lines of text.
// net/http/request.go:867
// NewRequest wraps NewRequestWithContext using context.Background.
// os/file.go:389-390
// Open opens the named file for reading.
// regexp/regexp.go:310-312
// MustCompile is like [Compile] but panics if the expression cannot be parsed.
// It simplifies safe initialization of global variables holding compiled regular
// expressions.
4. Doc Links (Square Bracket References)
Pattern name: [TypeName], [Package.Symbol], [Method] Links
Source citation: net/http/server.go#L65, os/file.go#L9
What it does: Doc comments use [SymbolName] to create hyperlinks to other
identifiers. These render as clickable links on pkg.go.dev.
Why: Cross-references help users navigate the API. Links are concise and don't clutter the plain-text rendering.
Anti-pattern: Using full URLs to godoc pages; not linking related types; over-linking (every mention of every type).
Code examples from source:
// net/http/server.go:65-70
// [Handler.ServeHTTP] should write reply headers and data to the [ResponseWriter]
// and then return. Returning signals that the request is finished; it
// is not valid to use the [ResponseWriter] or read from the
// [Request.Body] after or concurrently with the completion of the
// ServeHTTP call.
// os/file.go:9-11
// if a call that takes a file name fails, such as [Open] or [Stat], the error
// will include the failing file name when printed and will be of type
// [*PathError], which may be unpacked for more information.
5. Example Test Functions
Pattern name: func ExampleXxx() / func ExampleType_Method()
Source citation: regexp/example_test.go#L13, net/http/example_handle_test.go#L16
What it does: Functions named Example, ExampleXxx, or ExampleType_Method
in _test.go files serve as both executable tests and documentation. They include
an // Output: comment that go test verifies.
Why: Examples that compile, run, and are verified can never go stale. They appear
in go doc and pkg.go.dev alongside the relevant symbol. They teach by showing
real, working code.
When to Use
Triggers:
- You have a public function/type whose usage isn't obvious from the signature alone
- Your README examples have drifted from the actual API (broken examples in docs)
- You want examples that appear on pkg.go.dev AND are verified by
go test
Example — before:
// README.md (may be stale):
// ```go
// result := mylib.Process("input")
// fmt.Println(result.Data)
// ```
// ← compiles? who knows. API changed last month.
Example — after:
// example_test.go
func ExampleProcess() {
result := mylib.Process("input")
fmt.Println(result.Data)
// Output:
// processed: input
}
// ← go test verifies this compiles and produces the expected output
When NOT to Use
Don't write Example tests when:
- The function signature is self-explanatory (
func Max(a, b int) intdoesn't need an example) - The example would just restate the doc comment with no additional insight
- You're testing internal/unexported functions (examples must use the public API)
- The output is non-deterministic (timestamps, random values, goroutine ordering)
Over-application example:
// Pointless — the signature tells you everything
func ExampleAbs() {
fmt.Println(math.Abs(-5))
// Output:
// 5
}
Better alternative:
// Skip the example for trivial functions. Write examples for non-obvious behavior:
func ExampleAbs_nan() {
fmt.Println(math.Abs(math.NaN()))
// Output:
// NaN
}
// ← This teaches something surprising that the signature doesn't convey
Why: Examples exist to teach usage patterns that aren't obvious from the type signature and doc comment. Trivial examples add maintenance burden without teaching anything.
Anti-pattern: Examples that don't compile; examples without Output comments (not verified); examples in README that drift from reality.
Code examples from source:
// regexp/example_test.go:13-28
func Example() {
// Compile the expression once, usually at init time.
// Use raw strings to avoid having to quote the backslashes.
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)
fmt.Println(validID.MatchString("adam[23]"))
fmt.Println(validID.MatchString("eve[7]"))
fmt.Println(validID.MatchString("Job[48]"))
fmt.Println(validID.MatchString("snakey"))
// Output:
// true
// true
// false
// false
}
// net/http/example_handle_test.go:16-31
type countHandler struct {
mu sync.Mutex // guards n
n int
}
func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mu.Lock()
defer h.mu.Unlock()
h.n++
fmt.Fprintf(w, "count is %d\n", h.n)
}
func ExampleHandle() {
http.Handle("/count", new(countHandler))
log.Fatal(http.ListenAndServe(":8080", nil))
}
6. Inline Code Examples in Doc Comments
Pattern name: Indented Code Blocks in Comments
Source citation: os/file.go#L16, time/time.go#L928
What it does: Doc comments include indented code snippets (4 spaces) that render as preformatted code blocks in godoc.
Why: Shows typical usage patterns directly in the doc comment without requiring a separate Example test function. Good for short, illustrative snippets.
Anti-pattern: Non-indented code that doesn't render as code; examples too long for inline (use Example functions instead); examples that reference unexported symbols.
Code examples from source:
// os/file.go:16-21
// Here is a simple example, opening a file and reading some of it.
//
// file, err := os.Open("file.go") // For read access.
// if err != nil {
// log.Fatal(err)
// }
// time/time.go:925-933
// To count the number of units in a [Duration], divide:
//
// second := time.Second
// fmt.Print(int64(second/time.Millisecond)) // prints 1000
//
// To convert an integer number of units to a Duration, multiply:
//
// seconds := 10
// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
7. Deprecated Annotations
Pattern name: // Deprecated: ... in Doc Comments
Source citation: net/http/server.go#L57 (ErrWriteAfterFlush), os/file.go#L93
What it does: A paragraph starting with Deprecated: marks an identifier as
deprecated and explains what to use instead.
Why: Recognized by tooling (go vet, staticcheck, IDEs). Provides a migration path without breaking backward compatibility.
When to Use
Triggers:
- You have a better replacement for an existing function but can't remove the old one (semver)
- Users are still calling a function that has known issues or a superior alternative
- You want IDEs to show a strikethrough and linters to warn on usage
Example — before:
// Just delete it? Breaks everyone's code.
// Leave it silently? Users never learn about the better way.
Example — after:
// ParseDuration parses a duration string.
//
// Deprecated: Use [time.ParseDuration] instead, which handles
// all standard duration formats.
func ParseDuration(s string) (time.Duration, error) {
return time.ParseDuration(s) // delegate to the replacement
}
When NOT to Use
Don't deprecate when:
- The function still works fine and there's no better replacement yet
- You're deprecating to "clean up" the API without a migration path (users will just ignore it)
- The replacement has a different behavior or isn't a drop-in substitute (document the difference instead)
- You're on v0.x and can just remove it (pre-1.0 allows breaking changes)
Over-application example:
// Deprecated: Use NewClientV2 instead.
func NewClient(addr string) *Client { ... }
// But NewClientV2 has a completely different API:
func NewClientV2(cfg Config) (*ClientV2, error) { ... }
// Users can't just find-and-replace — this isn't a deprecation, it's a migration
Better alternative:
// NewClient creates a client with default configuration.
// For advanced configuration (TLS, timeouts, connection pooling),
// use [NewClientWithConfig] instead.
func NewClient(addr string) *Client {
return NewClientWithConfig(Config{Addr: addr})
}
Why: Deprecation means "there's a better way to do the same thing." If the replacement requires a fundamentally different approach, provide a migration guide — don't just slap Deprecated: on it and leave users stranded.
Anti-pattern: Removing deprecated APIs (breaks semver); deprecating without suggesting an alternative; using non-standard deprecation markers.
Code example from source:
// net/http/server.go:59-62
// Deprecated: ErrWriteAfterFlush is no longer returned by
// anything in the net/http package. Callers should not
// compare errors against this variable.
ErrWriteAfterFlush = errors.New("unused")
8. Error Documentation Convention
Pattern name: "If there is an error, it will be of type [*XxxError]"
Source citation: os/file.go#L388, 406
What it does: Functions document the concrete error type they return, enabling callers to type-assert for additional context.
Why: Go's error handling relies on type assertions and errors.Is/As. Knowing
the concrete type lets callers extract structured information (path, operation,
underlying cause).
Anti-pattern: Returning opaque errors with no documented structure; returning different error types from the same function without documenting which.
Code example from source:
// os/file.go:388-390
// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode [O_RDONLY].
// If there is an error, it will be of type [*PathError].
func Open(name string) (*File, error) {
9. Concurrency Documentation
Pattern name: "Safe for concurrent use" / Concurrency Guarantees
Source citation: net/http/transport.go#L79, os/types.go#L17, regexp/regexp.go#L77
What it does: Doc comments explicitly state the concurrency safety of a type or note exceptions where concurrent use is not safe.
Why: Go programs are inherently concurrent. Without explicit documentation, users must guess whether a type needs external synchronization.
Anti-pattern: Leaving concurrency safety undocumented; documenting it inconsistently across methods; saying "thread-safe" (Java-ism, use "safe for concurrent use by multiple goroutines").
Code examples from source:
// net/http/transport.go:72-73
// Transports should be reused instead of created as needed.
// Transports are safe for concurrent use by multiple goroutines.
// os/types.go:17
// The methods of File are safe for concurrent use.
// regexp/regexp.go:77-79
// A Regexp is safe for concurrent use by multiple goroutines,
// except for configuration methods, such as [Regexp.Longest].