有没有网站可以做试卷,公司设计效果图,活动营销案例100例,百度网站优化工具iota
Go让你用iota来使用枚举。
const (Guest iotaMemberModeratorAdmin
)虽然Go是明确的#xff0c;但iota似乎相对模糊。如果你以任何其他方式对const组进行排序#xff0c;你会引入副作用。在上面的例子中#xff0c;你仅仅对第一个参数Guest赋值了。你可以显式地给每…iota
Go让你用iota来使用枚举。
const (Guest iotaMemberModeratorAdmin
)虽然Go是明确的但iota似乎相对模糊。如果你以任何其他方式对const组进行排序你会引入副作用。在上面的例子中你仅仅对第一个参数Guest赋值了。你可以显式地给每个值分配一个数字来避免这个问题但这使iota变得过时。 iota对于用位运算定义的参数也很有效。
const (Guest 1 iota // 1Member // 2Moderator // 4Admin // 8
)// ...user.Roles Member | Moderator // 6位掩码是有效的有时也很有帮助。然而在大多数Web应用程序中它的使用情况与枚举不同。通常情况下你可以将所有的角色存储在一个列表中。它也会更容易阅读。
iota的主要问题是它在整数上工作没有防止传递无效的值。
func CreateUser(role int) error {fmt.Println(Creating user with role, role)return nil
}func main() {err : CreateUser(-1)if err ! nil {fmt.Println(err)}err CreateUser(42)if err ! nil {fmt.Println(err)}
}CreateUser会很乐意接受-1或42即使没有相应的角色。
当然我们可以在函数中验证这一点。但我们使用的是一种具有强类型的语言所以让我们利用它。在我们应用程序的上下文中用户角色远不止是一个模糊的数字。 反模式:整数枚举 不要使用基于iota的整数来表示不是连续的数字或标志的枚举。 我们可以引入一个类型来改进解决方案。
type Role uintconst (Guest Role iotaMemberModeratorAdmin
)它看起来更好但仍有可能传递任何任意的整数来代替Role。Go编译器在这里并没有帮助我们。
func CreateUser(role Role) error {fmt.Println(Creating user with role, role)return nil
}func main () {err : CreateUser(0)if err ! nil {fmt.Println(err)}err CreateUser(role.Role(42))if err ! nil {fmt.Println(err)}
}这个类型是对裸整数的改进但它仍然是一种幻觉。它并没有给我们提供任何保证说明这个角色是有效的。
哨兵值
因为iota从0开始Guest也是角色的零值。这使得我们很难检测到角色是空的还是有人传递了一个Guest值。 你可以通过从1开始计算来避免这种情况。甚至更好的是保留一个明确的哨兵值你可以进行比较不能误认为是一个实际的角色。
const (Unknown Role iotaGuestMemberModeratorAdmin
)func CreateUser(r role.Role) error {if r role.Unknown {return errors.New(no role provided)}fmt.Println(Creating user with role, r)return nil
} 策略明确的哨兵 为枚举的零值保留一个显式变量。 更准确的描述
枚举似乎是关于连续的整数但它很少是有效的表示。在网络应用中我们使用枚举来分组某种类型的可能变体。它们并不能很好地映射到数字上。 当你在API响应、数据库表或日志中看到一个3时很难理解其背景。你必须检查源码或过时的文档才能知道它是怎么回事。 在大多数情况下字符串比整数更有意义。无论你在哪里看到它一个有明确意义的表达都是显而易见的。既然iota无论如何也帮不了我们我们还可以使用人类可读的字符串。
type Role stringconst (Unknown Role unknownGuest Role guestMember Role memberModerator Role moderatorAdmin Role admin
)策略:使用字符串值而不是整数。 避免使用空白以方便解析和记录。使用camelCase、snake_case或kebab-case。 这样的表达对错误代码特别有用。{“error”:“user-not-found”}与{“error”:4102}相比是显而易见的. 然而该类型仍然可以容纳任何任意的字符串。
err CreateUser(super-admin)
if err ! nil {fmt.Println(err)
}基于结构的枚举
最后的迭代使用了结构体。它可以让我们在设计上保证代码的安全性。我们不需要检查传递的值是否正确。
type Role struct {slug string
}func (r Role) String() string {return r.slug
}var (Unknown Role{unknown}Guest Role{guest}Member Role{member}Moderator Role{moderator}Admin Role{admin}
)因为slug字段是私有的所以不可能从包的外部引用它。你能构建的唯一无效的角色是空的Role{}。
我们可以添加一个构造函数来创建一个基于slug的有效角色:
func FromString(s string) (Role, error) {switch s {case Guest.slug:return Guest, nilcase Member.slug:return Member, nilcase Moderator.slug:return Moderator, nilcase Admin.slug:return Admin, nil}return Unknown, errors.New(unknown role: s)
}策略:基于结构的枚举 将枚举封装在结构中以获得额外的编译时安全性。 这种方法在你处理业务逻辑时是完美的。保持结构在内存中始终处于有效状态使你的代码更容易操作和理解。检查枚举类型是否为空就足够了而且你可以确定它是一个正确的值。 这种方法有一个潜在的问题。如果我们用的是全局性的常亮这样的做法需要不断的赋值如下
roles.Guest role.Admin这样的话这个值说不准在哪里变化了都不知道。
校验方法
竟然上面的方法都无法满足我们的需求那么我们就加上一个校验方法避免运行时传入了非法的值即可
type Role stringconst (Unknown Role unknownGuest Role guestMember Role memberModerator Role moderatorAdmin Role admin
)var roleSet []Role{Unknown, Guest, Member, Moderator, Admin}func (role Role) Valid() bool {for _, r : range roleSet {if role r {return true}}return false
}这样的做法就可以满足我们对枚举更安全的需求了。