由于粗心,Go 变量作用域导致的问题

昨天写代码的时候因为变量作用域的问题被坑了好久,在这里记录一下,避免今后再犯。

先看下面这段代码,大致功能是为传进来的引用填充一个带有自增的 ID 的对象,同时这个 ID 中不能包含 4,自增 ID 是使用 Redis 的 incr 来维护的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func fillNewUserLiveRightAttribute(ctx context.Context, userLiveRight *po.UserLiveRight, right *po.LiveRight) error {
if right.Type == util.LiveRightTypeMystery {
var incrId int64
// 跳过 ID 中包含 4 的情况
for {
incrId, err := cache.GetUserLiveRightCacheRepository().GetUserLiveRightIncrID(ctx, right.ID)
if err != nil {
return err
}

incrIdStr := strconv.FormatInt(incrId, 10)

if !strings.Contains(incrIdStr, "4") {
break
}
}
userLiveRight.Attribute = &po.UserLiveRightAttribute{ID: incrId}
}
return nil
}

但是之后发现填充进去的 ID 永远是 0,检查了一下 Redis 中 那个自增 ID 也确实存在。

这里当时还饶了一下远路,因为那个 Attribute 字段在数据库中使用 jsonb 存储的,所以我前期先检查了插入时执行的 SQL 语句,发现每次 Attribute 都是打印的 {},就以为是我自己没有赋上值。真实的原因是我指定了 Attribute 中的 ID 字段在转 Json 时启用 omitempty ,即:

1
2
3
type UserLiveRightAttribute struct {
ID int64 `json:"id,omitempty"`
}

使用 omitempty 可以告诉 Marshal 函数如果 field 的值是对应类型的 zero-value,那么序列化之后的 JSON object 中不包含此 字段,所以 ID=0 转 Json 后自然就没有这个字段了。

回到为啥上边的代码拿到的 ID 总是 0 的问题:因为 for 中赋值的 incrId 是在一个新的作用域内,只在 for 的花括号内有效,退出 for 后拿到的是最开始初始化 incrId 的 0 值,这里使用的 := 进行的赋值,因为 err 是个新字段,所以并没有提示错误。

修复方法很简单,err 也在作用域外声明,里边使用 = 来赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func fillNewUserLiveRightAttribute(ctx context.Context, userLiveRight *po.UserLiveRight, right *po.LiveRight) error {
if right.Type == util.LiveRightTypeMystery {
var incrId int64
var err error
// 跳过 ID 中包含 4 的情况
for {
incrId, err = cache.GetUserLiveRightCacheRepository().GetUserLiveRightIncrID(ctx, right.ID)
if err != nil {
return err
}

incrIdStr := strconv.FormatInt(incrId, 10)

if !strings.Contains(incrIdStr, "4") {
break
}
}
userLiveRight.Attribute = &po.UserLiveRightAttribute{ID: incrId}
}
return nil
}