写点什么

「Go 易错集锦」如何正确设置枚举中的零值

作者:Go学堂
  • 2022-11-18
    北京
  • 本文字数:2218 字

    阅读完需:约 7 分钟

「Go易错集锦」如何正确设置枚举中的零值

枚举类型是由一组值组成的数据类型。在 Go 语言中,没有 enum 这样的关键字。然而,处理一组值最好的方法是用类型别名和常量。但是,我们无法达到其他语言所能达到的安全水平。这就是为什么我们在处理枚举值时必须要小心的原因。让我们来看一些相关的实践以及如何避免一些常见的错误。


下面列出了一周中周几的列表:

type Weekday int ①
const ( Monday Weekday = 0 ② Tuesday Weekday = 1 Wednesday Weekday = 2 Thursday Weekday = 3 Friday Weekday = 4 Saturday Weekday = 5 Sunday Weekday = 6)
复制代码


① 定义一个自定义的 Weekday 类型② 创建一个 Weekday 类型的 Modany 常量


创建一个 Weekday 类型的好处是可以强制让编译时做类型检查以及提高可读性。如果我们没有创建一个 Weekday 类型,那么下面的函数签名对于调用者来说可能会有一点模糊:

func GetCurrentWeekday() int {    // ...}
复制代码


一个 int 类型可以包含任何值,同时阅读者如果没有相关的阅读文档或者代码的话也不能猜出该函数返回的是什么值。相反,如果定义一个 Weekday 类型,那么就会使该函数的签名更清晰:

func getCurrentWeekday() Weekday {    // ...}
复制代码


在这个例子中,我们强制指定了返回具体的类型。


我们创建 Weekday 类型的枚举值的方法是比较合适的。然而,在 Go 中,还有有一种惯用的方法来声明枚举中的常量,那就是使用常量生成器 iota


注意:在本例中,我们还可以将 Weekday 声明为 uint32,以强制正值并确保每个 Weekday 变量分配 32 位。

iota

iota 用于创建一系列相关值,而无需明确设置这些值。 它指示编译器复制每个常量表达式,直到块结束或找到赋值。


下面是用 iota 的 Weekday 版本:

type Weekday intconst (    Monday Weekday = iota ①    Tuesday    Wednesday    Thursday    Friday    Saturday    Sunday)
复制代码


① 使用 iota 定义枚举值


itoa 的值从 0 开始并每行增加 1。此版本等同于第一个版本:

  • Monday = 0

  • Tuesday = 1

  • Wednesday = 3

  • etc


使用 iota 允许我们避免手动定义常量值。例如,在大的枚举中手动设置常量值是会容易出错的。进一步说,我们不用对每一个变量都重复指定 Weekday 类型:我们定义的所有变量都是一个 Weekday 类型。


注意:我们可以在更复杂的表达式中使用 iota。下面是从 Effective Go 中出现的一个关于处理 ByteSize 枚举值的例子:

type ByteSize float64const (  _ = iota ①  KB ByteSize = 1 << (10 * iota) ②  MB ③  GB  TB  PB  EB  ZB  YB)
复制代码

① 通过给 _ 赋值忽略第一行的值

② 在该行 iota 等于 1,因此 KB 被设置成 1 << (10 * 1)

③ 在这一行,iota 等于 2,本行将会重复上一行的表达式,因此 MB 被设置成了 1 << (10 * 2)


让我们看看在 Go 的枚举中如何处理未知值(unknown values)

Unknow 值

既然我们已经理解了在 Go 中处理枚举值的原理,让我们考虑下下面的例子。我们将实现一个 HTTP 处理以便将 JSON 格式的请求解码成 Request 结构体类型。该结构体将会包含一个 Weekday 类型的 Unknown 值。下面是第一版本的实现:

type Weekday int ①
const ( Monday Weekday = iota Tuesday Wednesday Thursday Friday Saturday Sunday Unknown ②)
type Request struct { ③ ID int `json:"id"` Weekday Weekday `json:"weekday"`}
func httpHandler(w http.REsponseWriter, r *http.Request) { ④ bytes, err != readBody(r) ⑤ if err != nil { w.WriteHeader(http.StatusBadRequest) return }
var request Request err = json.Unmarshal(bytes, &request) ⑥ if err != nil { w.WriteHeader(http.StatusBadRequest) return }
// Use Request}
复制代码


① 重用我们定义的 Weekday 枚举值

② 定义 Unknown 常量

③ 定义一个包含 Weekday 字段的 Request 结构体

④ 实现一个 HTTP 处理器

⑤ 读取请求体并返回一个[]byte

⑥ 解码 JSON 请求体


在这个例子中,我们创建了一个 Request 结构体,该结构体从一个 JSON 请求体中解码而来。这段代码非常完整有效。在例子中,我们可以接收一个 JSON 内容并正确解码:

{  "id": 1234,  "weekday": 0}
复制代码


这里,Weekday 字段的值会等于 0:Monday。现在,如果在 JSON 内容中不包含 weekday 字段会怎么样呢?

{  "Id": 1235}
复制代码


解析该内容的时候将不会引起任何错误。然而,在 Request 结构体中的 weekday 字段值将会被设置成一个 int 类型:0 值。因此,就像是在上次请求中的 Monday。


那我们应该如何区分请求中是传递的 Monday 还是没有就没有传递 weekday 字段呢?这个问题和我们定义 Weekday 枚举的方式有关。实际上,Unknown 是枚举值的最后一个值。因此,它的值应该等于 7


为了解决该问题,处理一个 unknown 的枚举值的最好的实践方法是将它设置成 0(int 类型的零值)。因此,我们应该按如下方式生命 Weekday 枚举值:

type Weekday int
const ( Unknown Weekday = iota ① Monday Tuesday Wednesday Thursday Friday Saturday Sunday)
复制代码


① Unknow 现在等于 0 了


如果 JSON 请求体中的 weekday 的值是空,那将会被解析成 Unknown;这就是我们所需要的。根据经验,枚举的未知值应该设置为枚举类型的零值。这样,我们就可以区分出显示值和缺失值了。


---特别推荐---

特别推荐:一个专注 go 项目实战、项目中踩坑经验及避坑指南、各种好玩的 go 工具的公众号。「Go 学堂」,专注实用性,非常值得大家关注。点击下方公众号卡片,直接关注。关注送《100 个 go 常见的错误》pdf 文档。

发布于: 刚刚阅读数: 3
用户头像

Go学堂

关注

关注「Go学堂」,学习更多编程知识 2019-08-06 加入

专注Go编程知识、案例、常见错误及原理分析。意在通过阅读更多优秀的代码,提高编程技能。同名公众号「Go学堂」期待你的关注

评论

发布
暂无评论
「Go易错集锦」如何正确设置枚举中的零值_Go_Go学堂_InfoQ写作社区