我在大厂做 CR——为什么建议使用枚举来替换布尔值

news/2024/10/16 2:15:25

使用枚举替换布尔值主要基于以下几个原因
● 可读性
● 可拓展性
● 安全防控

可读性

我们会定义 boolean 类型(truefalse)作为方法参数,虽然比较简洁,但有时候参数的含义往往不够清晰,造成阅读上的障碍,

比如:参数可能表示“是否开启某个功能”,但仅凭 truefalse 并不能一眼看出其真实意图:

setDisable(false):到底是禁用还是启用 --!
setInvalid(false):到底是无效还是有效 --!

相信我,这种“绕弯”的“双重否定”表达方式,一定会耗费你不多的脑细胞一会儿:)
当然你可能会说:“不使用否定的名词”,换成“直接表达”,setEnable(true),这一眼能识别是启用,非常直观;
是的,没错,但在我 10 余年的编程生涯里,相信我 setDisable(false) 遇到过无数次;
再举个例子:
下面代码你能“一眼知道”参数 true 代表什么含义吗?

public static void main(String[] args) {convert("12.3456", 2, true);
}/*** 将字符串转换成指定小数位数的值** @param value* @param scale* @param enableHalfUp 是否需要四舍五入* @return*/
public static String convertToValue(String value, int scale, boolean enableHalfUp) {if (enableHalfUp){//将字符串"四舍五入"换成指定小数位数的值}else{//将字符串"截断"换到指定小数位数的值}
}

当然,现在 IDE 都有比较好的提示,但从“可读性”角度,是不是只能进入到方法定义看注释去了解,甚至没有注释还得去翻代码去研究这个 boolean 到底是啥语义,参数再爆炸下,你能知道每个 boolean 类型参数代表什么吗?

convert("12.3456", 2, true,false,true,false,true);

这里额外扩展一句,木宛哥搞过一段时间的 iOS 开发,如果是 Objective-C 语言,方法命名采用了较为直观的格式,可以包含多个参数名称“线性叙事”,以提高可读性。这种情况,boolean 变量前往往有“名词修饰”,会容易理解,如下所示:
[NSString stringWithCString:"something" enableASCIIStringEncoding:true]

再从 OC 语言回过来,对于这个问题,让看看 JDK 是怎么设计的

public static void main(String[] args) {BigDecimal value = new BigDecimal("12.34567");//四舍五入到两位小数BigDecimal roundedValue = value.setScale(2, RoundingMode.HALF_UP);System.out.println(roundedValue);
}

看到了没,BigDecimalsetScale 方法,通过定义枚举:RoundingMode 代表转换规则,看到:RoundingMode.HALF_UP 一眼就知道要四舍五入,根本不需要看代码。
这样增加了可读性的,同时定义了枚举也支持更多扩展,如下马上引入第二点好处:可扩展

可扩展性

如果未来需要增加更多状态,使用 boolean 会受到扩展的限制

例如,如果当前有两个状态:enable(开)和 disable(关),而将来需要添加待机状态,使用 boolean 就显得不够灵活。枚举则很容易扩展,能够清晰地表示更多的状态。
使用 boolean 表达功能状态:

public void configureFeature(boolean enable) {if (enable) {// 开启功能} else {// 关闭功能}
}

使用枚举表达功能状态:

public enum FeatureMode {ENABLED,DISABLED,MAINTENANCE
}public void configureFeature(FeatureMode mode) {switch (mode) {case ENABLED:// 开启功能break;case DISABLED:// 关闭功能break;case MAINTENANCE:// 维护状态break;default:throw new IllegalArgumentException("Unknown mode: " + mode);}
}

类型安全

错误的使用 Boolean 包装类,有可能会引发空指针异常;

先抛一个问题:包装类 Boolean 有几种“值”?
Boolean 是包含两个值的枚举:Boolean.TRUEBoolean.FALSE但别忘了,还可以是 null

一个真实的线上故障,Boolean 在某些情况下被错误地使用,可能会造成空指针异常
例假设你正在修改一个老旧系统的某个方法,这个方法返回 Boolean,有几千行代码:

public static void main(String[] args) {if (checkIfMatched("Dummy")){System.out.println("matched");}
}/*** 老旧系统里一个异常复杂的方法,有几千行* @param str* @return*/
public static Boolean checkIfMatched(String str) {Boolean matched;//假设此处存在:复杂处理逻辑,暂时用dummy代替if ("Dummy".equals(str)) {matched = true;} else {matched = false;}return matched;
}

目前没问题,但当功能不断迭代后,复杂度也陡然上升,在某个特别的分支里,没有对 Boolean 赋值,至少在编译时是不会报错的:

public static void main(String[] args) {if (checkIfMatched("Dummy")) {System.out.println("matched");}
}/*** 老旧系统里一个异常复杂的方法,有几千行** @param str* @return*/
public static Boolean checkIfMatched(String str) {
Boolean matched = null;
//假设此处存在:复杂处理逻辑,暂时用 dummy 代替
if ("Dummy".equals(str)) {//模拟:代码在演进的时候,有可能存在 matched 未赋值情况if (false) {matched = true;}
} else {matched = false;
}
return matched;
}

这个时候,危险悄然而至,还记得上面的问题吗:
包装类 Boolean 有几种“值”?
现在 checkIfMatched() 方法在不同的情况下,方法会返回三个不同的值:true/false/null
这里 null 是非常危险的,如果上游使用如下方式判断条件,考虑下是否有问题?

if (checkIfMatched("Dummy")) {System.out.println("matched");
}

首先这里不会编译错误,但此处 if 条件处会自动拆箱,对于 null 值会得到 NullPointerException 异常;

小小总结

再回过头看:“哪些场景建议使用枚举来替换布尔值”,我认为要看功能点的易变程度去综合评估:“越容易变化,越不能让复杂度发散,越要由一处收敛,试想下一个 Boolean 的方法的变动是不是要评估所有上游的业务”;
所以并不是完全推翻布尔值,木宛哥在此也只是抛出一些代码的优化手段仅供参考。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

拼多多客服助手-自动回复消息插件

自动回复浏览器插件,支持拼多多客服网页版自动回复,解决回复率问题 gofly.v1kf.com十年开发经验程序员,离职全心创业中,历时三年开发出的产品《唯一客服系统》一款基于Golang+Vue开发的在线客服系统,软件著作权编号:2021SR1462600。一套可私有化部署的网站在线客服系统,…

数据结构 - 树,初探

树是分支分层的数据结构,包含根节点、子节点等术语,有深度、高度等属性。二叉树是树的一种,节点最多有两个子节点,有前序、中序、后序和层次遍历方式。树是一种非线性数据结构,是以分支关系定义的层次结构,因此形态上和自然界中的倒挂的树很像,而数据结构中树根向上树叶…

《使用Gin框架构建分布式应用》阅读笔记:p32-p51

《用Gin框架构建分布式应用》学习第3天,p32-p51总结,总计20页。 一、技术总结 1.Go知识点 slice, struct。 2.Gin知识点 (1)c.XML() 使用c.XML()解析cmx结构。 (2)c.ShouldBindJSON() 将struct转成json。 gin所有函数参考:https://pkg.go.dev/github.com/gin-gonic/gin 3.版…

数据采集与融合技术作业一

作业1 作业①【结合flask】 要求:用requests和BeautifulSoup库方法定向爬取给定网址(http://www.shanghairanking.cn/rankings/bcur/2020)的数据,屏幕打印爬取的大学排名信息。 1.1 代码和图片 import re import urllib.request from bs4 import BeautifulSoup from flask …

IDEA中如何让整个项目代码回退

背景:今天项目在做的时候,发现前面的代码部分有问题。但是已经不清楚自己改了哪些部分的代码了,这时候的一个好办法就是使用IDEA中的代码回退到之前的某一时刻。 做法:1、打开项目,如果回退整个项目的代码,右键项目 -> Local History -> show History2、现在可以看…

vue 动态加载路由,渲染左侧菜单栏

需求 我们在route文件中定义的路由是由子路由包裹进去的,它可能是无限级的。如何在vue的模板中渲染形成菜单栏。 如图: 解决方法 将菜单栏单独写成子组件(注意头部标签:element-plus中是el-menu)仍然在父组件中。将配置路由数据传入到子组件。子组件渲染一级路由。 一级路…

IDEA连接数据库后,在使用表的时候有时候未检测到表

我的这个产生的原因:之前做项目的时候检测到category表了,但后来数据库断开后,等再次连接上数据库,可以检测到数据库,但数据库中的表直接用,是检测不到的。 解决方法一:使用数据库中表的时候,可以 [数据库.数据库表],如下图所示,可以看到此时使用表就不爆红了。解决方…

DirectoryOpus插件:“照得标管理器”-海量照片分类管理好帮手!

照得标管理器 前言名词解释:“照得标管理器”,即:照片得到标签管理器,后文统一简称“照得标管理器”或“照得标”。  注:请不要和抖音上的“奥德彪”、“王德发”之类联系,我分享的是正经照片-得到-标签-管理器。有段时间作者赋闲在家,决定把留在电脑上的几万张照片整…