Go:文章Contexts and structs(三)

96 阅读2分钟

「这是我参与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 作为参数传入; 不要将它存储在结构中。