不要过度使用any类型
在Go语言中,具有零个方法的接口类型称为空接口(interface {}). 从Go1.18版本开始,出现了一个新的关键字 any, 它是 interface{} 的别名。因此,所有 interface{} 出现的地方都可以替换为 any. 然而,在很多情况下,any被认为是过度概括。本文将讨论过度使用any存在的问题。
像下面的代码,any 类型的变量可以保存任何类型的值。
代码语言:javascript复制func main() {
var i any
i = 42
i = "foo"
i = struct {
s string
}{
s: "bar",
}
i = f
_ = i
}
func f() {}
将任何类型的值赋值给 any 类型时,会丢失原来的类型信息, 这时需要类型断言才能从变量 i 中获取任何有用的信息。下面来看一个使用 any 不准确的具体例子,例子中将定义一个 Store 结构体 并且 它拥有 Get和Set 两个方法。用 Store 结构体存储 Customer 和 Contract 两种类型的对象,具体代码如下:
代码语言:javascript复制package store
type Customer struct{
// Some fields
}
type Contract struct{
// Some fields
}
type Store struct{}
func (s *Store) Get(id string) (any, error) {
// ...
}
func (s *Store) Set(id string, v any) error {
// ...
}
尽管上述代码可以编译成功,但我们应该花点时间审视一下函数签名,可以发现它接收并返回 any 类型对象, 缺乏表现力,不知道它真正存储的是什么类型的值。如果将来开发人员使用 Store 结构,他们可能必须深入阅读接口文档或代码了解如何使用这些方法。因此,接收或返回 any 类型不能传达有意义的信息。此外,在编译时没有保护措施,没法阻止调用者使用任何数据类型(例如int)调用这些方法:
代码语言:javascript复制s := store.Store{}
s.Set("foo", 42)
采用any类型,失去了Go作为静态类型语言的一些好处,我们应该避免使用 any 类型,尽可能使函数的签名明确。对于上面的例子,可能要为每种类型设置和获取各创建一个方法,示例代码如下。
代码语言:javascript复制func (s *Store) GetContract(id string) (Contract, error) {
// ...
}
func (s *Store) SetContract(id string, contract Contract) error {
// ...
}
func (s *Store) GetCustomer(id string) (Customer, error) {
// ...
}
func (s *Store) SetCustomer(id string, customer Customer) error {
// ...
}
对比起来,上面这个新版本富有表现力,减少了函数名抽象不清晰的问题。虽然结构体Store的方法增加了,但这没有什么问题。因为客户端可以使用接口创建自己的抽象,例如,如果客户端只对Contract方法感兴趣,可以定义下面的接口:
代码语言:javascript复制type ContractStorer interface {
GetContract(id string) (store.Contract, error)
SetContract(id string, contract store.Contract) error
}
那any类型在什么场景下是有用的呢?回到标准库中,来看看接收any参数的两个示例。第一个例子来至 encoding/json包,由于可以对任何类型进行序列化操作,所以Marshal函数接收的参数类型为any.
代码语言:javascript复制func Marshal(v any) ([]byte, error) {
// ...
}
第二个例子来至 database/sql 包,如果要执行参数化的查询,例如, SELECT * FROM FOO WHERE id = ?. 参数id可以是任何类型的数据,因此采用any作为接收参数。
代码语言:javascript复制func (c *Conn) QueryContext(ctx context.Context, query string,
args ...any) (*Rows, error) {
// ...
}
总结,如果确实需要接收或返回任何可能的类型,采用any类型可能会有所帮助。例如,当涉及到序列化或语句格式化时。一般来说,我们应该极力避免过度概括编写的代码。如果能够增强代码的表达性或者带来其他收益,少量重复的代码有时是更好的选择。