阴影

本解释使用了两个非正式的术语,stomping 和 shadowing。它们不是 Go 语言规范中的正式概念。 像许多编程语言一样,Go 有可变的变量:对一个变量的赋值会改变其值。

// Good:
func abs(i int) int {
    if i < 0 {
        i *= -1
    }
    return i
}

当使用短变量声明与 := 操作符时,在某些情况下不会创建一个新的变量。我们可以把这称为 stomping。当不再需要原来的值时,这样做是可以的。

// Good:
// innerHandler is a helper for some request handler, which itself issues
// requests to other backends.
func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
    // Unconditionally cap the deadline for this part of request handling.
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    ctxlog.Info("Capped deadline in inner request")
    // Code here no longer has access to the original context.
    // This is good style if when first writing this, you anticipate
    // that even as the code grows, no operation legitimately should
    // use the (possibly unbounded) original context that the caller provided.
    // ...
}

不过要小心在新的作用域中使用短的变量声明 : 这将引入一个新的变量。我们可以把这称为对原始变量的 “阴影”。块结束后的代码指的是原来的。这里是一个有条件缩短期限的错误尝试:

// Bad:
func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
    // Attempt to conditionally cap the deadline.
    if *shortenDeadlines {
        ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
        defer cancel()
        ctxlog.Info(ctx, "Capped deadline in inner request")
    }
    // BUG: "ctx" here again means the context that the caller provided.
    // The above buggy code compiled because both ctx and cancel
    // were used inside the if statement.
    // ...
}

正确版本的代码应该是:

// Good:
func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
    if *shortenDeadlines {
        var cancel func()
        // Note the use of simple assignment, = and not :=.
        ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
        defer cancel()
        ctxlog.Info(ctx, "Capped deadline in inner request")
    }
    // ...
}

在我们称之为 stomping 的情况下,因为没有新的变量,所以被分配的类型必须与原始变量的类型相匹配。有了 shadowing,一个全新的实体被引入,所以它可以有不同的类型。有意的影子可以是一种有用的做法,但如果能提高清晰度,你总是可以使用一个新的名字。

除了非常小的范围之外,使用与标准包同名的变量并不是一个好主意,因为这使得该包的自由函数和值无法访问。相反,当为你的包挑选名字时,要避免使用那些可能需要导入重命名或在客户端造成阴影的其他好的变量名称。

// Bad:
func LongFunction() {
    url := "https://example.com/"
    // Oops, now we can't use net/url in code below.
}