Go iota引起的线上事故
2023-07-19 本文已影响0人
qishuai
背景是这样的,前端页面有一个日志level的枚举值传递给后端,server端会对该枚举值的有效性进行校验,QA测试阶段使用了默认的level(也就是Info
),并没有测试出这个bug。但是线上业务需要配置一个Debug
的level,出现了提交失败的问题(后端校验为非法的枚举值)。示例代码如下:
枚举的定义:
type vlogLevel int
const (
VlogLevelUnknown = "Unknown"
VlogLevelDebug vlogLevel = iota
VlogLevelInfo
VlogLevelWarn
VlogLevelError
VlogLevelFatal
)
// FormatVlogLevel 返回vlog level的语义
func FormatVlogLevel(l int) string {
switch vlogLevel(l) {
case VlogLevelDebug:
return "Debug"
case VlogLevelInfo:
return "Info"
case VlogLevelWarn:
return "Warn"
case VlogLevelError:
return "Error"
case VlogLevelFatal:
return "Fatal"
}
return VlogLevelUnknown
}
枚举值校验的代码:
if data.FormatVlogLevel(req.LogLevel) == data.VlogLevelUnknown {
return nil, errors.New("invalid vlog_level value")
}
你可以暂停30s,看看是否发现了问题...
我当时看了好多遍,实在无法发现是哪个地方出现了问题,所以看似简单的问题才是最迷惑的,不知道你有同感没。
最后直接放大招,IDE debug搞起:前端传递Debug
的level是使用的是接口文档中约束的枚举: 0,根据我的理解FormatVlogLevel函数应该返回"Debug",然后直接返回了"Unknown";最后添加了万能输出代码: fmt.Println(l) fmt.Println(VlogLevelDebug)
。第一个输出是0,第二个输出是1,这。。。。
后来发现IDE给出了常量值的解析结果:
后来猜测可能和前面的那个枚举定义有关,将
VlogLevelUnknown = "Unknown"
调整到此const(...)
域以外,发现结果就符合预期了:iota
总结:
这种错误在编译环节是不会报错的,而且后期排查起来也非常困难。我尝试google了一下这类错误,发现并没有多少有价值的资料,是个不小心就可能引起大问题的坑。所以归纳几条建议,供大家参考:
- iota前不要定义任何常量值
- 如果有必要请直接使用字面量直接初始化常量值
- 编写单元测试,确保枚举值和预期严格一致
参考资料: