前言

这是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 mainimport "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。

但通常而言,为了代码的易读性和可维护性,咱们应该防止适度形象咱们的代码

举荐浏览

  • Go面试题系列,看看你会几题?
  • Go常见谬误第1篇:未知枚举值
  • Go常见谬误第2篇:benchmark性能测试的坑
  • Go常见谬误第3篇:go指针的性能问题和内存逃逸
  • Go常见谬误第4篇:break操作的注意事项
  • Go常见谬误第5篇:Go语言Error治理
  • Go常见谬误第6篇:slice初始化常犯的谬误
  • Go常见谬误第7篇:不应用-race选项做并发竞争检测
  • Go常见谬误第8篇:并发编程中Context应用常见谬误
  • Go常见谬误第9篇:应用文件名称作为函数输出
  • Go常见谬误第10篇:Goroutine和循环变量一起应用的坑
  • Go常见谬误第11篇:意外的变量遮蔽(variable shadowing)
  • Go常见谬误第12篇:如何破解箭头型代码
  • Go常见谬误第13篇:init函数的常见谬误和最佳实际
  • Go常见谬误第14篇:适度应用getter和setter办法
  • Go常见谬误第15篇:interface应用的常见谬误和最佳实际

开源地址

文章和示例代码开源在GitHub: Go语言高级、中级和高级教程。

公众号:coding进阶。关注公众号能够获取最新Go面试题和技术栈。

集体网站:Jincheng's Blog。

知乎:无忌。

福利

我为大家整顿了一份后端开发学习材料礼包,蕴含编程语言入门到进阶常识(Go、C++、Python)、后端开发技术栈、面试题等。

关注公众号「coding进阶」,发送音讯 backend 支付材料礼包,这份材料会不定期更新,退出我感觉有价值的材料。

发送音讯「进群」,和同行一起交流学习,答疑解惑。

References

  • https://livebook.manning.com/...
  • https://go.dev/ref/spec#Prede...
  • https://go101.org/article/key...,understand%20and%20parse%20user%20code.&text=They%20can%20be%20categorized%20as,code%20elements%20in%20Go%20programs.