Go常见错误第16篇:any的常见错误和最佳实践
创始人
2024-05-08 17:23:22
0

前言

这是Go常见错误系列的第16篇:any的常见错误和最佳实践。

素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。

本文涉及的源代码全部开源在:Go常见错误源代码,欢迎大家关注公众号,及时获取本系列最新更新。

常见错误和最佳实践

Go语言中,没有方法的接口类型是空接口,也就是大家熟知的interface{}

从Go 1.18开始,定义了一个预声明标识符(Predeclared identifiers):any。

any实际上是空接口的别名,所以任何用了interface{}的地方都可以把interface{} 替换为any。

func main() {var i anyi = 42i = "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) // panicfmt.Println(f)
}

我们看看下面这个例子,体会下使用any带来的问题。

package storetype 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/book/100-go-mistakes-how-to-avoid-them/chapter-2/
  • https://go.dev/ref/spec#Predeclared_identifiers
  • https://go101.org/article/keywords-and-identifiers.html#:~:text=Keywords%20are%20the%20special%20words,understand%20and%20parse%20user%20code.&text=They%20can%20be%20categorized%20as,code%20elements%20in%20Go%20programs.

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...