Go中更安全的枚举

news/2024/9/18 14:36:58/ 标签: golang, 安全, 开发语言

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.ppmy.cn/news/1525209.html

相关文章

【前端】vue+html+js 实现table表格展示,以及分页按钮添加

一. 问题描述 数据条数太多显示到页面上时可能会渲染较慢&#xff0c;因此需要截取数据进行展示。 二. 代码写法 思路&#xff1a;按照上述图示思路&#xff0c;需要有两个数据列表&#xff0c;一个存储的是所有的列表数据&#xff0c;一个存储的是展示的数据列表&#xff0c…

jQuery UI API 文档

关于《jQuery UI API 文档》&#xff0c;我找到了一些有用的信息。jQuery UI 是建立在 jQuery JavaScript 库上的一组用户界面交互、特效、小部件及主题。如果您是 jQuery 新手&#xff0c;建议您先查看 jQuery 教程。目前&#xff0c;我找到的资料主要是关于 jQuery UI 1.10 版…

【加密社】深入理解TON智能合约 (FunC语法)

king: 摘要&#xff1a;在TON&#xff08;TheOpenNetwork&#xff09;区块链平台中&#xff0c;智能合约扮演着举足轻重的角色。本文将通过分析一段TON智能合约代码 带领读者学习dict&#xff08;字典&#xff09;和list&#xff08;列表&#xff09;在FunC语言中的用法&#x…

LeetCode_sql_day24(1212.查询球队积分)

描述 表: Teams ------------------------- | Column Name | Type | ------------------------- | team_id | int | | team_name | varchar | ------------------------- team_id 是该表具有唯一值的列。 表中的每一行都代表一支独立足球队。表: Matches…

人工智能 | 搭建企业内部的大语言模型系统

大纲 开源大语言模型大语言模型管理私有大语言模型服务部署方案 开源大语言模型 担心安全与隐私&#xff1f;可私有部署的开源大模型 商业大模型&#xff0c;不支持私有部署 ChatGPTClaudeGoogle Gemini百度问心一言 开源大模型&#xff0c;支持私有部署 MistralMeta Llama…

【LeetCode】:面试题 16.05. 阶乘尾数

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 好久没有写文章了&#xff0c;今天碰见了一道有趣的题目&#xff0c;写下来分享一下。 &#x1f3c6;1.问题描…

【QT】系统-上

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;QT 目录 &#x1f449;&#x1f3fb;事件QWidget中常见的事件 &#x1f449;&#x1f3fb;处理鼠标事件&#xff1a;leaveEvent和enterEvent&#x1f449;&a…

简单代码实现视频转图片_py

目录 1.安装OpenCV 环境要求 安装命令 验证安装 2. OpenCV用法 3.实现程序 博主最近在研究深度学习&#xff0c;需要收集数据集进行处理&#xff0c;但一张张拍照真是太麻烦了 就想着&#xff0c;哎&#xff0c;能不能写一个程序&#xff0c;把视频转成图片不就行了&am…

tabBar设置底部导航栏

如果应用是一个多 tab 应用&#xff0c;可以通过 tabBar 配置项指定一级导航栏&#xff0c;以及 tab 切换时显示的对应页&#xff0c;简单来说就是像美团外卖下面的导航栏一样可以任意切换 1.首先创建三个页面&#xff0c;在页面里面可以写一些东西或者放一张图片方便区分。 2.…

http网络请求与下载进度

Http_request 目录 一、XMLHttpRequest 在使用 Fetch API 进行网络请求时&#xff0c;原生的 Fetch API 并不直接支持获取下载进度的功能&#xff0c;因为 Fetch API 主要是基于 Promise 的&#xff0c;它主要关注于请求的成功或失败&#xff0c;以及响应数据的处理&#xff…

微信小程序开发——比较两个数字大小

在这里我们使用的工具是 需要自行安装和配置。 在微信小程序中比较两个数字大小有以下几种方式&#xff1a; 一、普通条件判断 在小程序的.js 文件中&#xff0c;先定义两个数字&#xff0c;如let num1 5; let num2 3;。通过if - else if - else语句&#xff0c;根据num1与…

MVC设计模式与delegate,tablview,Appdelegate,SceneDelegate

一、MVC MVC就是Model&#xff08;模型&#xff09;、View&#xff08;视图&#xff09;、Controller&#xff08;控制器&#xff09; 例如上面的 excel表&#xff0c; 数据、数据结构就是模型Model 根据数据形成的直观的、用户能直接看见的柱形图是视图View 数据构成的表格…

假期学习-- iOS 通知详解

iOS 通知详解 数据结构 从我们之前使用通知的流程和代码来看&#xff0c;通知其实就是一个单例&#xff0c;方便随时访问。 NSNotificationCenter&#xff1a;消息中心 这个单例类中主要定义了两个表&#xff0c;一个存储所有注册通知信息的表的结构体&#xff0c;一个保存…

模版方法模式template method

学习笔记&#xff0c;原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架&#xff0c; 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

神经网络学习笔记——如何设计、实现并训练一个标准的前馈神经网络

1.从零设计并训练一个神经网络https://www.bilibili.com/video/BV134421U77t/?spm_id_from333.337.search-card.all.click&vd_source0b1f472915ac9cb9cdccb8658d6c2e69 一、如何设计、实现并训练一个标准的前馈神经网络&#xff0c;用于手写数字图像的分类&#xff0c;重…

【SpringBoot3】面向切面 AspectJ AOP 使用详解

文章目录 一、AspectJ介绍二、简单使用步骤1、引入依赖2、定义一个Aspect3、开启AOP支持 三、AOP 核心概念四、切点&#xff08;Pointcut&#xff09;1. execution2. within3. this & target4. args & args5. within & target & annotation 五、通知&#xff0…

Unix时间戳与C语言的time.h库函数

目录 Unix时间戳介绍 UTC/GMT 时间与秒计数器转换代码 time.h库函数 Unix时间戳介绍 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒 时间戳存储在一个秒计数器中&#xff0c;秒计数器…

15.5 创建监控控制平面的service

本节重点介绍 : k8s中service的作用和类型创建k8s控制平面的service 给prometheus采集用&#xff0c; 类型clusterIp kube-schedulerkube-controller-managerkube-etcd service的作用 Kubernetes Service定义了这样一种抽象&#xff1a; Service是一种可以访问 Pod逻辑分组…

MATLAB求解0-1线性规划问题的详细分析

引言 0-1线性规划是整数规划中的一种特殊形式&#xff0c;它广泛应用于资源分配、工厂选址、投资组合优化、物流运输等多个领域。0-1线性规划的特点是&#xff0c;决策变量只能取0或1的离散值&#xff0c;通常用于描述“是-否”决策问题。随着计算机技术的发展&#xff0c;数学…

《仙境传说RO:新启航》游戏攻略:VMOS云手机提升装备获取辅助!自由交易,辅助挂机操作!

在《仙境传说RO&#xff1a;新启航》中&#xff0c;想要快速提升战斗力并获取顶级装备&#xff0c;玩家需要熟练掌握多种获取资源与提升角色的途径。为了让玩家更加轻松地享受游戏&#xff0c;VMOS云手机推出了专属定制版云手机&#xff0c;内置游戏安装包&#xff0c;不需要重…