Go iota引起的线上事故

背景是这样的,前端页面有一个日志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给出了常量值的解析结果:

iota

后来猜测可能和前面的那个枚举定义有关,将VlogLevelUnknown = "Unknown"调整到此const(...)域以外,发现结果就符合预期了:
iota

总结:
这种错误在编译环节是不会报错的,而且后期排查起来也非常困难。我尝试google了一下这类错误,发现并没有多少有价值的资料,是个不小心就可能引起大问题的坑。所以归纳几条建议,供大家参考:

  • iota前不要定义任何常量值
  • 如果有必要请直接使用字面量直接初始化常量值
  • 编写单元测试,确保枚举值和预期严格一致

参考资料:

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 开发环境 GOROOT: go的安装目录, go原生的工具在该目录下GOPATH: 通常存放自己开发的代码或第三方...
    Never_Yg阅读 251评论 0 0
  • Go语言数据类型 Go语言本质是用C语言编写的一套高级开发语言, 所以Go语言中的数据类型大部分都是由C语言演变而...
    极客江南阅读 948评论 0 6
  • 第4天iota关键字 package main import "fmt" func main() { /* i...
    3天时间阅读 268评论 0 0
  • 一、常量概述 * 常量是一个固定值,在编译器就确定结果.声明时必须赋值且结果不可以改变. * 因为常量在编译器就确...
    小陈工阅读 211评论 0 1
  • 能力模式 选择题 【初级】下面属于关键字的是()A. funcB. defC. structD. class 参考...
    灵魂深灵阅读 5,318评论 2 5