likes
comments
collection
share

我被空切片(empty slice)和零值切片(nil slice)坑了的故事

作者站长头像
站长
· 阅读数 12

写在前面:工作中遇到了一个由empty slice和nil slice引起的bug,本篇文档记录其中的排查过程以及底层原理。

起因

QA提了一个缺陷单到我这里,登上环境去看页面的报错,是前端抛出来的错误: can not return null for non-nullable filed Resources。初步排查下来应该是Resources这个字段值前端在代码里规定返回值应该是个空数组,但是后端接口处却返回了null,因此才引起了这个报错。

排查过程

和前端同学一沟通,果然是这个问题。排查后端代码,发现在某些场景下Resources这个值返回的是一个空值,代码里是这么写的:

var resources []string
resources = append(resources,getPodBound(req.Name, req.Namespace, pods)...)

我们对resources变量的初始化采用的var关键字进行初始化,熟悉go的同学都知道,var关键字初始化变量默认是返回变量的零值,那[]string的零值是空数组还是空指针nil呢?

在go中,数组是值类型,而切片slice是指针类型,它的零值是空指针nil,那[]string的零值自然也就是空指针了。 可以简单用以下代码进行验证:


var str1 []string
fmt.Println(str1 == nil) // true

修复方法

那正确的写法应该是怎么样呢?如果我们想让初始化的切片是空切片(empty slice)而非零值切片(nil slice),可以采用:=的方式进行初始化,如下所示:

str2 := make([]string, 0)
fmt.Println(str2 == nil) // false

可以看到,此时切片的默认值不等于,而是空切片[].

反思

空切片和零值切片确实是在日常的工作中比较容易混淆的,且后期排查过程中也会比较麻烦。二者通过 fmt.Println 打印输出的结果都是 [],而且它们的 length 值和 capacity 值都是一样的,因此就会认为这二者是一个东西。

此外empty slice和nil slice都能正常的遍历数据和追加数据,但是在encoding/json库对二者进行Marshal操作时,empty slice会返回[],而nil slice则会返回null,因此在接口中就有可能引起前端的报错。

type Test struct {
   Data []string
}

func main() {
   var str1 []string
   str2 := make([]string, 0)
   test1 := &Test{Data: str1}
   test2 := &Test{Data: str2}

   res1, _ := json.Marshal(test1)
   res2, _ := json.Marshal(test2)

   fmt.Println(string(res1)) // {"Data":null}
   fmt.Println(string(res2)) // {"Data":[]}
}
转载自:https://juejin.cn/post/7249584280837111868
评论
请登录