Documentation
source ↗Documentation
Conventions
This section augments the decisions document’s commentary section.
Go code that is documented in familiar style is easier to read and less likely to be misused than something misdocumented or not documented at all. Runnable examples show up in Godoc and Code Search and are an excellent way of explaining how to use your code.
Parameters and configuration
Not every parameter must be enumerated in the documentation. This applies to:
- function and method parameters
- struct fields
- APIs for options
Document the error-prone or non-obvious fields and parameters by saying why they are interesting.
In the following snippet, the highlighted commentary adds little useful information to the reader:
// Bad:
// Sprintf formats according to a format specifier and returns the resulting
// string.
//
// format is the format, and data is the interpolation data.
func Sprintf(format string, data ...any) string
However, this snippet demonstrates a code scenario similar to the previous where the commentary instead states something non-obvious or materially helpful to the reader:
// Good:
// Sprintf formats according to a format specifier and returns the resulting
// string.
//
// The provided data is used to interpolate the format string. If the data does
// not match the expected format verbs or the amount of data does not satisfy
// the format specification, the function will inline warnings about formatting
// errors into the output string as described by the Format errors section
// above.
func Sprintf(format string, data ...any) string
Consider your likely audience in choosing what to document and at what depth. Maintainers, newcomers to the team, external users, and even yourself six months in the future may appreciate slightly different information from what is on your mind when you first come to write your docs.
See also:
Contexts
It is implied that the cancellation of a context argument interrupts the
function it is provided to. If the function can return an error, conventionally
it is ctx.Err().
This fact does not need to be restated:
// Bad:
// Run executes the worker's run loop.
//
// The method will process work until the context is cancelled and accordingly
// returns an error.
func (Worker) Run(ctx context.Context) error
Because that is implied, the following is better:
// Good:
// Run executes the worker's run loop.
func (Worker) Run(ctx context.Context) error
Where context behavior is different or non-obvious, it should be expressly documented if any of the following are true.
-
The function returns an error other than
ctx.Err()when the context is cancelled:// Good: // Run executes the worker's run loop. // // If the context is cancelled, Run returns a nil error. func (Worker) Run(ctx context.Context) error -
The function has other mechanisms that may interrupt it or affect lifetime:
// Good: // Run executes the worker's run loop. // // Run processes work until the context is cancelled or Stop is called. // Context cancellation is handled asynchronously internally: run may return // before all work has stopped. The Stop method is synchronous and waits // until all operations from the run loop finish. Use Stop for graceful // shutdown. func (Worker) Run(ctx context.Context) error func (Worker) Stop() -
The function has special expectations about context lifetime, lineage, or attached values:
// Good: // NewReceiver starts receiving messages sent to the specified queue. // The context should not have a deadline. func NewReceiver(ctx context.Context) *Receiver // Principal returns a human-readable name of the party who made the call. // The context must have a value attached to it from security.NewContext. func Principal(ctx context.Context) (name string, ok bool)Warning: Avoid designing APIs that make such demands (like contexts not having deadlines) from their callers. The above is only an example of how to document this if it cannot be avoided, not an endorsement of the pattern.
Concurrency
Go users assume that conceptually read-only operations are safe for concurrent use and do not require extra synchronization.
The extra remark about concurrency can safely be removed in this Godoc:
// Len returns the number of bytes of the unread portion of the buffer;
// b.Len() == len(b.Bytes()).
//
// It is safe to be called concurrently by multiple goroutines.
func (*Buffer) Len() int
Mutating operations, however, are not assumed to be safe for concurrent use and require the user to consider synchronization.
Similarly, the extra remark about concurrency can safely be removed here:
// Grow grows the buffer's capacity.
//
// It is not safe to be called concurrently by multiple goroutines.
func (*Buffer) Grow(n int)
Documentation is strongly encouraged if any of the following are true.
-
It is unclear whether the operation is read-only or mutating:
// Good: package lrucache // Lookup returns the data associated with the key from the cache. // // This operation is not safe for concurrent use. func (*Cache) Lookup(key string) (data []byte, ok bool)Why? A cache hit when looking up the key mutate a LRU cache internally. How this is implemented may not be obvious to all readers.
-
Synchronization is provided by the API:
// Good: package fortune_go_proto // NewFortuneTellerClient returns an *rpc.Client for the FortuneTeller service. // It is safe for simultaneous use by multiple goroutines. func NewFortuneTellerClient(cc *rpc.ClientConn) *FortuneTellerClientWhy? Stubby provides synchronization.
Note: If the API is a type and the API provides synchronization in entirety, conventionally only the type definition documents the semantics.
-
The API consumes user-implemented types of interfaces, and the interface’s consumer has particular concurrency requirements:
// Good: package health // A Watcher reports the health of some entity (usually a backend service). // // Watcher methods are safe for simultaneous use by multiple goroutines. type Watcher interface { // Watch sends true on the passed-in channel when the Watcher's // status has changed. Watch(changed chan<- bool) (unwatch func()) // Health returns nil if the entity being watched is healthy, or a // non-nil error explaining why the entity is not healthy. Health() error }Why? Whether an API is safe for use by multiple goroutines is part of its contract.
Cleanup
Document any explicit cleanup requirements that the API has. Otherwise, callers won’t use the API correctly, leading to resource leaks and other possible bugs.
Call out cleanups that are up to the caller:
// Good:
// NewTicker returns a new Ticker containing a channel that will send the
// current time on the channel after each tick.
//
// Call Stop to release the Ticker's associated resources when done.
func NewTicker(d Duration) *Ticker
func (*Ticker) Stop()
If it is potentially unclear how to clean up the resources, explain how:
// Good:
// Get issues a GET to the specified URL.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// resp, err := http.Get("http://example.com/")
// if err != nil {
// // handle error
// }
// defer resp.Body.Close()
// body, err := io.ReadAll(resp.Body)
func (c *Client) Get(url string) (resp *Response, err error)
See also:
Errors
Document significant error sentinel values or error types that your functions return to callers so that callers can anticipate what types of conditions they can handle in their code.
// Good:
package os
// Read reads up to len(b) bytes from the File and stores them in b. It returns
// the number of bytes read and any error encountered.
//
// At end of file, Read returns 0, io.EOF.
func (*File) Read(b []byte) (n int, err error) {
When a function returns a specific error type, correctly note whether the error is a pointer receiver or not:
// Good:
package os
type PathError struct {
Op string
Path string
Err error
}
// Chdir changes the current working directory to the named directory.
//
// If there is an error, it will be of type *PathError.
func Chdir(dir string) error {
Documenting whether the values returned are pointer receivers enables callers to
correctly compare the errors using [errors.Is], [errors.As], and
[package cmp]. This is because a non-pointer value is not equivalent to a
pointer value.
Note: In the Chdir example, the return type is written as error rather
than *PathError due to
how nil interface values work.
Document overall error conventions in the package’s documentation when the behavior is applicable to most errors found in the package:
// Good:
// Package os provides a platform-independent interface to operating system
// functionality.
//
// 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.
package os
Thoughtful application of these approaches can add extra information to errors without much effort and help callers avoid adding redundant annotations.
See also:
Preview
Go features a documentation server. It is recommended to preview the documentation your code produces both before and during the code review process. This helps to validate that the godoc formatting is rendered correctly.
Godoc formatting
Godoc provides some specific syntax to format documentation.
-
A blank line is required to separate paragraphs:
// Good: // LoadConfig reads a configuration out of the named file. // // See some/shortlink for config file format details. -
Test files can contain runnable examples that appear attached to the corresponding documentation in godoc:
// Good: func ExampleConfig_WriteTo() { cfg := &Config{ Name: "example", } if err := cfg.WriteTo(os.Stdout); err != nil { log.Exitf("Failed to write config: %s", err) } // Output: // { // "name": "example" // } } -
Indenting lines by an additional two spaces formats them verbatim:
// Good: // Update runs the function in an atomic transaction. // // This is typically used with an anonymous TransactionFunc: // // if err := db.Update(func(state *State) { state.Foo = bar }); err != nil { // //... // }Note, however, that it can often be more appropriate to put code in a runnable example instead of including it in a comment.
This verbatim formatting can be leveraged for formatting that is not native to godoc, such as lists and tables:
// Good: // LoadConfig reads a configuration out of the named file. // // LoadConfig treats the following keys in special ways: // "import" will make this configuration inherit from the named file. // "env" if present will be populated with the system environment. -
A single line that begins with a capital letter, contains no punctuation except parentheses and commas, and is followed by another paragraph, is formatted as a header:
// Good: // The following line is formatted as a heading. // // Using headings // // Headings come with autogenerated anchor tags for easy linking.
Signal boosting
Sometimes a line of code looks like something common, but actually isn’t. One of
the best examples of this is an err == nil check (since err != nil is much
more common). The following two conditional checks are hard to distinguish:
// Good:
if err := doSomething(); err != nil {
// ...
}
// Bad:
if err := doSomething(); err == nil {
// ...
}
You can instead “boost” the signal of the conditional by adding a comment:
// Good:
if err := doSomething(); err == nil { // if NO error
// ...
}
The comment draws attention to the difference in the conditional.