使用枚举替换布尔值主要基于以下几个原因

● 可读性

● 可拓展性

● 安全防控

可读性

我们会定义 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 的方法的变动是不是要评估所有上游的业务”;

所以并不是完全推翻布尔值,木宛哥在此也只是抛出一些代码的优化手段仅供参考。

我在大厂做 CR——为什么建议使用枚举来替换布尔值的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议——建议28:理解延迟求值和主动求值之间的区别

    建议28:理解延迟求值和主动求值之间的区别 要理解延迟求值(lazy evaluation)和主动求值(eager evaluation),先看个例子: List<, , , , , , , , ...

  2. Qt程序调试之Q_ASSERT断言(它是一个宏,接受布尔值,当其中的布尔值为真时,便什么也不做)

    在使用Qt开发大型软件时,难免要调试程序,以确保程序内的运算结果符合我们的预期.在不符合预期结果时,就直接将程序断下,以便我们修改. 这就用到了Qt中的调试断言 - Q_ASSERT. 用一个小例子来 ...

  3. sqlHelper做增删改查,SQL注入处理,存储值,cookie,session

    一.存储值 eg:登录一个页面,在进入这个页面之前你怎么知道它登没登录呢?[在登录成功之后我们把状态保存起来] 存储值得方式有两种,一种是cookie,一种是session 1.1区别: 代码: if ...

  4. 建议3:正确处理Javascript特殊值---(1)正确使用NaN和Infinity

    NaN时IEEE 754中定义的一个特殊的数量值.他不表示一个数字,尽管下面的表达式返回的是true typeof(NaN) === 'number' //true 该值可能会在试图将非数字形式的字符 ...

  5. Android Fragment 生命周期及其API使用(建议使用自定义View替换Fragment)

    我为什么不主张使用Fragment Fragment:( Fragment就相当于一个有生命周期的View,它的生命周期被所在的Activity的生命周期管理 ) 生命周期回调说明: onAttach ...

  6. Android Fragment 生命周期及其正确使用(建议使用自定义View替换Fragment)

    使用Fragment 官方例子中显示: 例如:一个学生Fragment,需要传入studentId,进行http请求显示,那么setArguments后防止杀掉Fragment后,参数为0,显示不了数 ...

  7. javascript做的一个根据table中某个td的值为日期时的倒计时

    JavaScript代码: <script> window.onload = window.onload = function () { getTdValue(); } //根据传过来的天 ...

  8. 记录要做的事情,把sql字符串替换写成工具网页。

    之前使用的是java的本地控制台进行sql占位符的替换. 现在我想换个方式,想到了两种. 第一种是使用java +jsp进行替换,前台输出. 第二种是把java代码改成js代码,反正也不用访问数据库. ...

  9. Enum(枚举类型)的基本应用

    一.前言 在我们日常的开发过程中,我们经常定义使用常量:在Effective Java建议用枚举来替换常量的使用,提高我们代码的质量,总结一下枚举定义常量的基本使用 二.枚举类型说明      1.枚 ...

  10. Java 相关注意事项小结

    程序是一系列有序指令的集合: Java主要用于开发两类程序: 1)桌面应用程序2)Internet应用程序1,Java程序:三步走,编写--编译--运行:2,使用记事本开发:1)以.java为后缀名保 ...

随机推荐

  1. 【SqlServer】02 SSMS工具基本使用入门

    之前的安装中除了SqlServer,还有一个SSMS管理工具 数据库的访问依赖于工具 SSMS提供了两种登陆方式: 创建用户: 删除用户: 创建数据库: 删除数据库: 创建表: 设置表的字段,字段名称 ...

  2. 【Vue】04 模块化开发演变

    JS最初的目的是用来做表单验证和动画效果,可以让网页更加生动. 但是使用Ajax,前后端分离,页面承担了更多的事情,JS的代码量暴增,代码管理维护逐渐困难 我们需要将JS代码抽取出来,模块化处理, 但 ...

  3. 【Zookeeper】01 概述 & 基础部署

    背景: 随着互联网技术的发展,企业对计算机系统的计算,存储能力要求越来越高,各大IT企业都在追求高并发,海量存储的极致, 在这样的背景下,单纯依靠少量高性能单机来完成计算机,云计算的任务已经无法满足需 ...

  4. 【转载】 arch linux下nvidia 驱动死机问题

    原文地址: https://my.oschina.net/u/947271/blog/288063 ================================================== ...

  5. ubuntu23.04/22.04下安装docker engine

    官方网址: https://docs.docker.com/engine/install/ubuntu/ 2023年12月1日更新 -- Ubuntu 23.04 # Add Docker's off ...

  6. python 音频处理(2)——提取PPG特征之whisper库的使用(2.1)

    提取PPG特征之--whisper库的使用(2.1) 1 安装对应的包 方法一(自用): 直接pip即可: pip install openai-whisper 成功后如下图所示 方法二: 当时用了他 ...

  7. 微服务开发手册之GRPC 荐

    GRPC是一个高性能.通用的开源RPC框架,基于HTTP/2协议标准和Protobuf序列化协议开发,支持众多的开发语言. @[TOC] 1 简介 在GRPC框架中,客户端可以像调用本地对象一样直接调 ...

  8. C语言操作时间函数time.ctime,实现定时执行某个任务小例子

    时间操作函数在实际项目开发中会经常用到,最近做项目也正好用到就正好顺便整理一下. 时间概述 由上图可知: 通过系统调用函数time()可以从内核获得一个类型为time_t的1个值,该值叫calenda ...

  9. docker部署宝塔面板

    环境准备: 系统 rocky:9.2 部署流程: 1.安装docker dnf -y install yum-utils yum-config-manager --add-repo http://mi ...

  10. JavaScript设计模式样例十三 —— 模版方法模式

    模板方法模式(Template Method Pattern) 定义:一个抽象类公开定义了执行它的方法的方式/模板.它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行.目的:一些方法通用 ...