CONFIG_JSON = "JSON"
)
发现上面的问题了吗,没错,只有CONFIG_XML
是ConfigType
类型的!
但因为无类型常量有自动类型匹配,所以你的代码目前为止运行起来一点问题也没有,这也导致你没发现这个缺陷,直到:
// 给enum加个方法,现在要能获取常量的名字,以及他们在配置数组里的index
type ConfigType string
func (c ConfigType) Name() string {
switch c {
case CONFIG_XML:
return "XML"
case CONFIG_JSON:
return "JSON"
}
return "invalid"
}
func (c ConfigType) Index() int {
switch c {
case CONFIG_XML:
return 0
case CONFIG_JSON:
return 1
}
return -1
}
目前为止一切安好,然后代码炸了:
fmt.Println(CONFIG_XML.Name())
fmt.Println(CONFIG_JSON.Name()) // !!! error
编译器不乐意,它说:CONFIG_JSON.Name undefined (type untyped string has no field or method Name)
。
为什么呢,因为上下文里没明确指定类型,fmt.Println
的参数要求都是any
,所以这里用了无类型常量的默认类型。当然在其他地方也一样,CONFIG_JSON.Name()
这个表达式是无法推断出CONFIG_JSON
要匹配成什么类型的。
这一切只是因为你少写了一个类型。
这还只是第一个坑,实际上因为只要是目标类型可以接受的值,就可以赋值给目标类型,那么出现这种代码也不奇怪:
const NET_ERR_MESSAGE = "site is unreachable"
func doWithConfigType(t ConfigType)
doWithConfigType(CONFIG_JSON)
doWithConfigType(NET_ERR_MESSAGE) // WTF???
一不小心就能把错得离谱的参数传进去,如果你没想到这点而做好防御的话,生产事故就理你不远了。
第一个坑还可以通过把常量定义写全每个都加上类型来避免,第二个就只能靠防御式编程凑活了。
看到这里,你也应该猜到我当年闯的是什么祸了。好在及时发现,最后补全声明 + 防御式编程在出事故前把问题解决了。
最后也许有人会问,golang实现enum这么折磨?没有别的办法了吗?
当然有,而且有不少,其中一个比较著名的是stringer
: https://pkg.go.dev/golang.org/x/tools/cmd/stringer
这个工具也只能解决一部分问题,但以及比什么都做不了要强太多了。
总结
无类型常量会自动转换到匹配的类型,这会带来意想不到的麻烦。
一点建议:
- 如果可以的话,尽量在定义常量时给出类型,尤其是你自定义的类型,int这种看情况可以不写
- 尝试用工具去生成enum,一定要自己写过过瘾的话记得处理必然存在的例外情况。
这就是golang的大道至简,简单它自己,坑都留给你。
参考
https://go.dev/ref/spec#Representability