本节是对决策文件的注释部分的补充。 以熟悉的风格记录的 Go 代码比那些错误记录或根本没有记录的代码更容易阅读,更不容易被误用。可运行的实例会出现在 Godoc 和代码搜索中,是解释如何使用你的代码的绝佳方式。
不是每个参数都必须在文档中列举出来。这适用于
将易出错或不明显的字段和参数记录下来,说说它们为什么有趣。
在下面的片段中,突出显示的注释对读者来说没有增加什么有用的信息:
// 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 ...interface{}) string
然而,这个片段展示了一个与之前类似的代码场景,其中评论反而说明了一些不明显或对读者有实质性帮助的东西:
// 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 ...interface{}) string
在选择文档的内容和深度时,要考虑到你可能的受众。维护者、新加入团队的人、外部用户,甚至是六个月后的你,可能会与你第一次来写文档时的想法略有不同的信息。
也请参见。
这意味着取消一个上下文参数会中断提供给它的函数。如果该函数可以返回一个错误,习惯上是ctx.Err()
。
这个事实不需要重述:
// 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
因为这句话是隐含的,所以下面的说法更好:
// Good:// Run executes the worker's run loop.func (Worker) Run(ctx context.Context) error
如果上下文行为是不同的或不明显的,应该明确地记录下来:
如果函数在取消上下文时返回一个除ctx.Err()
以外的错误:
// 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
如果该功能有其他机制,可能会中断它或影响其生命周期:
// 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) errorfunc (Worker) Stop()
如果该函数对上下文的生命周期、脉络或附加值有特殊期望:
// 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)
警告:避免设计对其调用者提出这种要求(比如上下文没有截止日期)的API。以上只是一个例子,说明在无法避免的情况下该如何记录,而不是对该模式的认可。
Go 用户认为概念上的只读操作对于并发使用是安全的,不需要额外的同步。
在这个 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
然而,变异操作并不被认为对并发使用是安全的,需要用户考虑同步化。 同样地,这里可以安全地删除关于并发的额外注释:
// Grow grows the buffer's capacity.//// It is not safe to be called concurrently by multiple goroutines.func (*Buffer) Grow(n int)
强烈鼓励在以下情况下提供文档:
目前还不清楚该操作是只读的还是变异的。
// 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)
为什么?在查找密钥时,缓存命中会在内部突变一个 LRU 缓存。这一点是如何实现的,对所有的读者来说可能并不明显。
同步是由 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) *FortuneTellerClient
为什么?Stubby 提供了同步性。
注意:如果API是一个类型,并且 API 完整地提供了同步,传统上只有类型定义记录了语义。
该 API 消费用户实现的接口类型,并且该接口的消费者有特殊的并发性要求:
// Good:package health// A Watcher reports the health of some entity (usually a backen 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}
为什么?一个 API 是否能被多个 goroutines 安全使用是其契约的一部分。
记录 API 的任何明确的清理要求。否则,调用者不会正确使用 API,导致资源泄漏和其他可能的错误。
调出由调用者决定的清理工作:
// 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) *Tickerfunc (*Ticker) Stop()
如果有可能不清楚如何清理资源,请解释如何清理:
复制代码// 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)