前言
这是Go常见错误系列的第16篇:any的常见错误和最佳实践。
素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。
本文涉及的源代码全部开源在:Go常见错误源代码,欢迎大家关注公众号,及时获取本系列最新更新。
常见错误和最佳实践
Go语言中,没有方法的接口类型是空接口,也就是大家熟知的interface{}
。
从Go 1.18开始,定义了一个预声明标识符(Predeclared identifiers):any。
any实际上是空接口的别名,所以任何用了interface{}
的地方都可以把interface{}
替换为any。
func main() {
var i any
i = 42
i = "foo"
i = struct {
s string
}{
s: "bar",
}
i = f
_ = i
}
func f() {}
很多场景里,如果直接用any,会带来代码的过度抽象。
Rob Pike在Gopherfest 2015上,曾经分享过他的观点:
interface{} says nothing.
常见错误
给any类型的变量赋值的时候,我们其实失去了所有类型信息,需要依赖类型断言(type assertion)来获取类型信息。
类型断言即t, ok := i.(T)
,代码示例如下所示:
package main
import "fmt"
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // panic
fmt.Println(f)
}
我们看看下面这个例子,体会下使用any带来的问题。
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 {
// ...
}
这段代码里,我们定义了一个Store结构体,这个结构体有2个方法Get和Set,可以用来设置和获取Customer和Contract这2个结构体类型的变量。
示例代码计划用Get和Set方法来设置和查询Customer结构体和Contract结构体。
Get和Set方法虽然只存储Customer和Contrac这2个结构体类型,但是使用了any作为方法参数和方法返回值类型。
如果一个开发者只是看到函数签名里参数和返回值为any,会很容易误以为可以存储和查询任何类型的变量,比如int。
但实际上Get和Set方法的实现只是为了服务于Customer和Contract结构体。
这就是any类型带来的问题,因为隐藏了类型信息,开发者看到any要特别留意,仔细阅读代码和文档,才能避免出错。
比如有的开发者可能出现以下误用的情况:
s := store.Store{}
s.Set("foo", 42)
Set的第2个参数虽然是any,但实际是要存储Customer和Contract类型的结构体,但由于参数为any,有的开发者可能就会直接存一个int类型,那就和代码设计的预期不符。
使用any会丢失类型信息,Go作为静态类型语言的优势就被影响了。
如下代码,其实是更好的设计。看代码一目了然,很方便知道每个方法存储和查询的是什么类型的结构体。
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 {
// ...
}
最佳实践
any当然也不是一无是处,我们来看看Go标准库里的以下几个关于any的使用场景。
-
第一个例子是encoding/json这个package里的Marshal函数。
因为Marshal函数可以操作任何类型,所以参数类型为any。
func Marshal(v any) ([]byte, error) { // ... }
-
第二个例子是database/sql包里的QueryContext方法。
如果这个方法的第2个query参数是格式化字符串,比如
SELECT * FROM FOO WHERE id = ?
,由于查询语句里?
的值其实可以是任何类型,所以后面的args参数是any类型。func (c *Conn) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) { // ... }
总结
如果具体的使用场景的确是适合任意类型,那可以使用any。
但通常而言,为了代码的易读性和可维护性,我们应该避免过度抽象我们的代码
推荐阅读
开源地址
文章和示例代码开源在GitHub: Go语言初级、中级和高级教程。
公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。
个人网站:Jincheng’s Blog。
知乎:无忌。
References
今天的文章Go常见错误第16篇:any的常见错误和最佳实践分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/15363.html