背景是這樣的,前端頁面有一個日志level的枚舉值傳遞給后端,server端會對該枚舉值的有效性進(jìn)行校驗(yàn),QA測試階段使用了默認(rèn)的level(也就是Info),并沒有測試出這個bug。但是線上業(yè)務(wù)需要配置一個Debug的level,出現(xiàn)了提交失敗的問題(后端校驗(yàn)為非法的枚舉值)。示例代碼如下:
枚舉的定義:
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
}
枚舉值校驗(yàn)的代碼:
if data.FormatVlogLevel(req.LogLevel) == data.VlogLevelUnknown {
return nil, errors.New("invalid vlog_level value")
}
你可以暫停30s,看看是否發(fā)現(xiàn)了問題...
我當(dāng)時看了好多遍,實(shí)在無法發(fā)現(xiàn)是哪個地方出現(xiàn)了問題,所以看似簡單的問題才是最迷惑的,不知道你有同感沒。
最后直接放大招,IDE debug搞起:前端傳遞Debug的level是使用的是接口文檔中約束的枚舉: 0,根據(jù)我的理解FormatVlogLevel函數(shù)應(yīng)該返回"Debug",然后直接返回了"Unknown";最后添加了萬能輸出代碼: fmt.Println(l) fmt.Println(VlogLevelDebug)。第一個輸出是0,第二個輸出是1,這。。。。
后來發(fā)現(xiàn)IDE給出了常量值的解析結(jié)果:

iota
后來猜測可能和前面的那個枚舉定義有關(guān),將
VlogLevelUnknown = "Unknown"調(diào)整到此const(...)域以外,發(fā)現(xiàn)結(jié)果就符合預(yù)期了:
iota
總結(jié):
這種錯誤在編譯環(huán)節(jié)是不會報(bào)錯的,而且后期排查起來也非常困難。我嘗試google了一下這類錯誤,發(fā)現(xiàn)并沒有多少有價(jià)值的資料,是個不小心就可能引起大問題的坑。所以歸納幾條建議,供大家參考:
- iota前不要定義任何常量值
- 如果有必要請直接使用字面量直接初始化常量值
- 編寫單元測試,確保枚舉值和預(yù)期嚴(yán)格一致
參考資料: