Best way to enumerate constants in Go

huangapple go评论92阅读模式
英文:

Best way to enumerate constants in Go

问题

我正在学习其他编程语言之后开始学习Go语言。Go语言有一种非常优雅的方式来创建带有数值的常量,如下所示:

const (
    _      = iota    // 0 被跳过
    Sunday           // 1
    Monday           // 2
    ...
)

这种方式很容易编写,但是真的容易维护吗?例如,如果你突然在现有值之间插入新值,那么所有后续的值都会改变。而且很难找到这个问题,只有仔细阅读差异才能发现。或者在其他部分出现错误。我该如何提取这些带有名称的值并在程序的其他部分或数据库中使用呢?
例如,对于PostgreSQL,我可以定义:

CREATE TYPE color AS ENUM ('', 'Sunday', 'Monday');

只是为了举例说明。例如,Python有枚举类型:

from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

然后你可以像这样使用它:Color.RED。接下来,我可以获取所有的值:

list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

这使我能够"内省"模块并在数据库中创建易于阅读的枚举。例如,对于PostgreSQL,我可以定义:

CREATE TYPE color AS ENUM ('RED', 'GREEN', 'BLUE');

我该如何做到以下几点:

  1. 反射获取Go语言常量的名称?
  2. 创建不会漂移其值的防错常量?只能手动修复吗?
  3. 或许有更优雅的方式来做这件事吗?

谢谢。

英文:

I'm starting to learn Go after other languages. Go has a very elegant way of creating constants with numeric values like:

const (
    _      = iota    // 0 and is skipped
    Sunday           // 1
    Monday           // 2
    ...
)

This is very easy to write, but is it really easy to maintain? For example, if you suddenly insert new value to between present, all subsequent will change their values. And it will be hard to find, only scrupulous diff reading can reveal it. Or errors on other parts. How can I extract these values with names and use in other parts of a program, or in database?
For example for PostgreSQL I can define:

CREATE TYPE color AS ENUM (&#39;&#39;, &#39;Sunday&#39;, &#39;Monday&#39;);

Just to illustrate an idea. For example, Python has Enum type:

from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

Then you may use it like Color.RED. Next I can take all values:

list(Color)
[&lt;Color.RED: 1&gt;, &lt;Color.BLUE: 2&gt;, &lt;Color.GREEN: 3&gt;]

This allows me to "introspect" to module and create easily-readable enums in databases. For example for PostgreSQL I can define:

CREATE TYPE color AS ENUM (&#39;RED&#39;, &#39;GREEN&#39;, &#39;BLUE&#39;);

How can I:

  1. Reflect golang constants names?
  2. Make error-proof constants which cannot drift their values? Only fix them manually?
  3. May be there's an idiomatic way to do it better?

Thanks.

答案1

得分: 3

  1. 你可以使用stringer来生成名称,链接为https://godoc.org/golang.org/x/tools/cmd/stringer。

  2. 不确定你的意思是什么?大多数语言都允许你漂移值,如果你希望数字保持不变,你应该始终在列表末尾添加,或者像在Python中一样,你可以显式地将每个值设置为一个数字,而不是使用iota。

  3. 实际上,在Go语言中,枚举并不是很好用。

英文:
  1. You can use stringer to generate the names https://godoc.org/golang.org/x/tools/cmd/stringer

  2. Not sure what you mean? Most languages will allow you to drift values, you should always add to the end of the list if you want the number to stay constant, or like in python you could explicitly set each value to a number instead of using iota.

  3. Not really, enums just aren't great in golang

答案2

得分: 2

只是一个建议,但可能对你有帮助:我发现如果明确数值看起来像位掩码,常量在以后更不容易被改变/破坏,你可以在Go语言中这样做:

const (
    Red   = 1 << iota
    Green
    Blue
) // values = 1, 2, 4

虽然这不是最美观的声明方式,但你也可以包含掩码常量:

const (
    Red, RedMask = 1 << iota, 1 << iota - 1 // Red = 1, RedMask = 0
    Green, GreenMask                       // Green = 2, mask = 1
    Blue, BlueMask                         // 4, 3
    RGB, RGBMask                           // 8, 7
)

结合为这些常量指定的type可能会很有用:

type ColourConst int
const (
    Red, RMask ColourConst = 1 << iota, 1 << iota-1
    // ...
    _, All
)

// 类似这样(未经测试,可能不正确)
func (c ColourConst) validate() error {
    mask := int(c) & (-1 * int(c))
    if mask != int(c) {
        return errors.New("Colour is not a single bit value")
    }
    if s := c & All; s != c {
        return errors.New("Colour is not in range")
    }
}

我知道星期几不太可能用作位掩码,但这样做可以减少人们破坏代码的可能性。至少,它传达了常量的顺序很重要,这就是iota的作用。

英文:

Just a suggestion, but something that might help in your case: I find that constants are less likely to be changed/broken later on if it's clear that the values look like bit masks, which you can do in go like so:

const (
    Red   = 1 &lt;&lt; iota
    Green
    Blue
) // values = 1, 2, 4

And, even though it's not the prettiest of declarations, you can include the mask constants, too

const (
    Red, RedMask = 1 &lt;&lt; iota, 1&lt;&lt; iota - 1 // Red = 1, RedMask = 0
    Green, GreenMask                       // Green = 2, mask = 1
    Blue, BlueMask                         // 4, 3
    RGB, RGBMask                           // 8, 7
)

This, coupled with a designated type for these constants might be useful:

type ColourConst int
const (
    Red, RMask ColourConst = 1 &lt;&lt; iota, 1 &lt;&lt; iota-1
    // ...
    _, All
)

// something like this (untested, might not be correct)
func (c ColourConst) validate() error {
    mask := int(c) &amp; (-1 * int(c))
    if mask != int(c) {
        return errors.New(&quot;Colour is not a single bit value&quot;)
    }
    if s := c &amp; All; s != c {
        return errors.New(&quot;Colour is not in range&quot;)
    }
}

I know that the days of the week are unlikely to be used as bitmasks, but it makes it less likely for people to break the code. At the very least, it communicates that the order of the constants matter, that's what iota does IMO.

答案3

得分: 1

解决方案。
有优秀的模块Enumer和Enumelinter。

英文:

Solution.
There're excellent modules Enumer and Enumelinter

huangapple
  • 本文由 发表于 2017年2月3日 03:58:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/42011235.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定