乐趣区

关于go:Go常见错误第16篇any的常见错误和最佳实践

前言

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

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

举荐浏览

  • 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.
退出移动版