「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。
net/http 包选择了 context-in-struct 方法,它提供了一个有用的案例研究。 让我们看看 net/http 的 Do。 在引入 context.Context 之前,Do 定义如下:
// Do sends an HTTP request and returns an HTTP response [...]
func (c *Client) Do(req *Request) (*Response, error)
在 Go 1.7 之后,如果不是因为它会破坏向后兼容性,Do 可能看起来像下面这样:
// Do sends an HTTP request and returns an HTTP response [...]
func (c *Client) Do(ctx context.Context, req *Request) (*Response, error)
但是,保持向后兼容性并遵守 Go 1 的兼容性承诺对于标准库至关重要。 因此,维护者选择在 http.Request 结构上添加 context.Context 以允许支持 context.Context 而不会破坏向后兼容性:
// A Request represents an HTTP request received by a server or to be sent by a client.
// ...
type Request struct {
ctx context.Context
// ...
}
// NewRequestWithContext returns a new Request given a method, URL, and optional
// body.
// [...]
// The given ctx is used for the lifetime of the Request.
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
// Simplified for brevity of this article.
return &Request{
ctx: ctx,
// ...
}
}
// Do sends an HTTP request and returns an HTTP response [...]
func (c *Client) Do(req *Request) (*Response, error)
当改造您的 API 以支持上下文时,将 context.Context 添加到结构中可能是有意义的,如上所述。 但是,请记住首先考虑复制您的函数,这允许在不牺牲实用性和理解性的情况下以向后兼容的方式改进 context.Context。 例如:
// Call uses context.Background internally; to specify the context, use
// CallContext.
func (c *Client) Call() error {
return c.CallContext(context.Background())
}
func (c *Client) CallContext(ctx context.Context) error {
// ...
}
结论
上下文context可以轻松地将重要的跨库和跨 API 信息沿调用堆栈传递。 但是,它必须一致且清晰地使用,以保持可理解、易于调试和有效。
当作为方法中的第一个参数而不是存储在结构类型中传递时,用户可以充分利用其可扩展性,以便通过调用堆栈构建强大的调用取消、截止日期和元数据信息树。 而且,最重要的是,当它作为参数传入时,它的范围被清楚地理解,从而导致堆栈上下的清晰理解和可调试性。
在设计带有上下文的 API 时,请记住以下建议:将 context.Context 作为参数传入; 不要将它存储在结构中。