Go中更安全的枚举

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 = "unknown"Guest     Role = "guest"Member    Role = "member"Moderator Role = "moderator"Admin     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 = "unknown"Guest     Role = "guest"Member    Role = "member"Moderator Role = "moderator"Admin     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
}

这样的做法就可以满足我们对枚举更安全的需求了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/623992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2719. 统计整数数目

给你两个数字字符串 num1 和 num2 &#xff0c;以及两个整数 max_sum 和 min_sum 。如果一个整数 x 满足以下条件&#xff0c;我们称它是一个好整数&#xff1a; num1 < x < num2min_sum < digit_sum(x) < max_sum. 请你返回好整数的数目。答案可能很大&#xff…

【LeetCode:76. 最小覆盖子串 | 滑动窗口】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【Element】el-input 限定输入是 只能是数字和符号 -

<template><div><el-input v-model"inputValue" input"handleInput"></el-input></div> </template><script> export default {data() {return {inputValue: };},methods: {handleInput(value) {// 使用正则表达…

LeetCode刷题--- 粉刷房子

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述动…

Julia系列16:Julia与python/c互调

1 Julia调用python 通过PyCall包&#xff0c;Julia可以直接调用Python包。例如&#xff1a; using PyCall np pyimport("numpy") x np.linspace(1, 10, 10)数值、布尔、字符串、IO stream、函数、元组、数组或列表、以及包含这些类型的字典等&#xff0c;它们都会…

LeetCode.82 删除排序链表中的重复元素 二

LeetCode.82 删除排序链表中的重复元素 二 题目 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* deleteDuplicates(struct ListNode* head) {if(head NULL) return head;struct ListNode* _…

Gradle 安装及源替换详解

在软件开发的过程中&#xff0c;Gradle 是一个强大且灵活的构建工具&#xff0c;被广泛应用于 Java、Android、Kotlin 等项目。本文将为您提供 Gradle 的安装步骤以及如何更换源的详细说明和代码示例。 一. Gradle 安装步骤 以下是在 Windows 操作系统上安装 Gradle 的步骤&am…

帆软报表11.0.19增加postgres数据源方案

项目使用postgres数据库&#xff0c;帆软报表集成开发时需要手工增加该数据源。 https://help.fanruan.com/finereport/doc-view-2563.html 但增加数据源后测试报告无此驱动&#xff0c;经查看文档&#xff0c;现在是通过驱动管理来上传&#xff0c; 但新版又不允许上传驱动JAR…

「HDLBits题解」Always casez

本专栏的目的是分享可以通过HDLBits仿真的Verilog代码 以提供参考 各位可同时参考我的代码和官方题解代码 或许会有所收益 题目链接&#xff1a;Always casez - HDLBits // synthesis verilog_input_version verilog_2001 module top_module (input [7:0] in,output reg [2:0]…

ai智能语音机器人系统的话术怎样设置效果比较好

设置一个AI智能语音机器人的话术&#xff0c;以实现最佳效果&#xff0c;涉及以下几个关键方面&#xff1a; 1. 自然语言处理&#xff08;NLP&#xff09;&#xff1a;AI机器人的话术需要能够理解和处理用户的自然语言输入。使用NLP技术来识别语义、意图和实体&#xff0c;并针…

Pointnet++改进注意力机制系列:全网首发SE通道注意力机制 |即插即用,实现有效涨点!

简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入SE注意力机制,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步骤

走迷宫(c语言)

前言&#xff1a; 制作一个迷宫游戏是一个有趣的编程挑战。首先&#xff0c;我们需要设计一个二维数组来表示迷宫的布局&#xff0c;其中每个元素代表迷宫中的一个格子。我们可以使用不同的值来表示空格、墙壁和起点/终点。接下来&#xff0c;我们需生成迷宫。在生成迷宫的过程…

C# Guid生成唯一值用例

GUID&#xff08;全局唯一标识符&#xff09;是一个128位的数字&#xff0c;通常用来唯一标识信息。GUID 的生成算法保证了在相同的时空条件下&#xff0c;基本上不会生成重复的值。这是因为GUID的生成算法结合了多种不同的信息&#xff0c;包括时间戳、计算机的 MAC 地址、随机…

2024年【上海市安全员C3证】模拟考试题及上海市安全员C3证模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年上海市安全员C3证模拟考试题为正在备考上海市安全员C3证操作证的学员准备的理论考试专题&#xff0c;每个月更新的上海市安全员C3证模拟考试题库祝您顺利通过上海市安全员C3证考试。 1、【多选题】《上海市建设…

STC15系列单片机:定时器/计数器16位自动重装载模式

一、定时器与计数器的理解 STC15系列单片机内部有5个16位定时器/计数器&#xff0c;分别是T0、T1、T2、T3、T4。 定时器与计数器&#xff0c;东西还是同一个东西&#xff0c;只是用法和功效不一样&#xff0c;就好比黄瓜&#xff0c;既可以内服也可以外敷&#xff0c;黄瓜还是…

深入理解 go chan

go 里面&#xff0c;在实际程序运行的过程中&#xff0c;往往会有很多协程在执行&#xff0c;通过启动多个协程的方式&#xff0c;我们可以更高效地利用系统资源。 而不同协程之间往往需要进行通信&#xff0c;不同于以往多线程程序的那种通信方式&#xff0c;在 go 里面是通过…

Rust-所有权和移动语义

什么是所有权 拿C语言的代码来打个比方。我们可能会在堆上创建一个对象&#xff0c;然后使用一个指针来管理这个对象&#xff1a; Foo *p make_object("args");接下来&#xff0c;我们可能需要使用这个对象&#xff1a; use_object(p);然而&#xff0c;这段代码之…

继承、修饰符、工具类、jar包

目录 1.继承 2.修饰符 3.工具类 4.jar包的制作与使用 1.继承 是什么 1.面向对象的三大特征之一&#xff08;封装、继承、多态&#xff09; 2.可以使得子类具有父类的属性和方法&#xff0c;还可以在子类中重新定义&#xff0c;追加属性和方法。 继承的格式 public class F…

Camtasia2024最新版本如何进行电脑录制屏幕?

在现在的网络互联网时代&#xff0c;越来越多的人走上了自媒体的道路。有些自媒体人会自己在网络上录制精彩视频&#xff0c;也有一些人会将精彩、热门的电影剪辑出来再加上自己给它的配音&#xff0c;做成大家喜欢看的电影剪辑片段。相信不管大家是自己平时有独特的爱好也好、…

瑞吉外卖笔记系列(1) —— 环境配置,后台登录和退出的功能实现

本文档主要介绍软件开发整体流程和瑞吉外卖项目&#xff0c;开发环境搭建步骤&#xff0c;以及简单的后台系统功能实现 文章目录 一、软件开发整体介绍1.1软件开发流程1.2 角色分工1.3 软件环境 二、瑞吉外卖项目介绍2.1 项目介绍2.2 产品原型展示2.3 技术选型2.4 功能架构2.5 …