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

立山  论坛元老 | 2024-10-16 01:21:16 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1029|帖子 1029|积分 3087

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

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

我们会定义 boolean 范例(true 或 false)作为方法参数,虽然比较简便,但有时候参数的含义往往不够清楚,造成阅读上的障碍,
比如:参数可能表现“是否开启某个功能”,但仅凭 true 和 false 并不能一眼看出其真实意图:
setDisable(false):到底是禁用还是启用 --!
setInvalid(false):到底是无效还是有效 --!
信任我,这种“绕弯”的“双重否定”表达方式,一定会泯灭你不多的脑细胞一会儿
当然你可能会说:“不使用否定的名词”,换成“直接表达”,setEnable(true),这一眼能识别是启用,非常直观;
是的,没错,但在我 10 余年的编程生活里,信任我 setDisable(false) 遇到过无数次;
再举个例子:
下面代码你能“一眼知道”参数 true 代表什么含义吗?
  1. public static void main(String[] args) {
  2.     convert("12.3456", 2, true);
  3. }
  4. /**
  5. * 将字符串转换成指定小数位数的值
  6. *
  7. * @param value
  8. * @param scale
  9. * @param enableHalfUp 是否需要四舍五入
  10. * @return
  11. */
  12. public static String convertToValue(String value, int scale, boolean enableHalfUp) {
  13.     if (enableHalfUp){
  14.         //将字符串"四舍五入"换成指定小数位数的值
  15.     }else{
  16.         //将字符串"截断"换到指定小数位数的值
  17.     }
  18. }
复制代码
当然,现在 IDE 都有比较好的提示,但从“可读性”角度,是不是只能进入到方法定义看注释去了解,乃至没有注释还得去翻代码去研究这个 boolean 到底是啥语义,参数再爆炸下,你能知道每个 boolean 范例参数代表什么吗?
  1. convert("12.3456", 2, true,false,true,false,true);
复制代码
这里额外扩展一句,木宛哥搞过一段时间的 iOS 开辟,如果是 Objective-C 语言,方法定名采用了较为直观的格式,可以包含多个参数名称“线性叙事”,以进步可读性。这种情况,boolean 变量前去往有“名词修饰”,会容易理解,如下所示:
[NSString stringWithCString:"something" enableASCIIStringEncoding:true]
再从 OC 语言回过来,对于这个问题,让看看 JDK 是怎么设计的
  1. public static void main(String[] args) {
  2.     BigDecimal value = new BigDecimal("12.34567");
  3.     //四舍五入到两位小数
  4.     BigDecimal roundedValue = value.setScale(2, RoundingMode.HALF_UP);
  5.     System.out.println(roundedValue);
  6. }
复制代码
看到了没,BigDecimal 的 setScale 方法,通过定义枚举:RoundingMode 代表转换规则,看到:RoundingMode.HALF_UP 一眼就知道要四舍五入,根本不需要看代码。
这样增加了可读性的,同时定义了枚举也支持更多扩展,如下马上引入第二点利益:可扩展
可扩展性

如果未来需要增加更多状态,使用 boolean 会受到扩展的限制
比方,如果当前有两个状态:enable(开)和 disable(关),而未来需要添加待机状态,使用 boolean 就显得不够灵活。枚举则很容易扩展,可以或许清楚地表现更多的状态。
使用 boolean 表达功能状态:
  1. public void configureFeature(boolean enable) {
  2.     if (enable) {
  3.         // 开启功能
  4.     } else {
  5.         // 关闭功能
  6.     }
  7. }
复制代码
使用枚举表达功能状态:
  1. public enum FeatureMode {
  2.     ENABLED,
  3.     DISABLED,
  4.     MAINTENANCE
  5. }
  6. public void configureFeature(FeatureMode mode) {
  7.     switch (mode) {
  8.         case ENABLED:
  9.             // 开启功能
  10.             break;
  11.         case DISABLED:
  12.             // 关闭功能
  13.             break;
  14.         case MAINTENANCE:
  15.             // 维护状态
  16.             break;
  17.         default:
  18.             throw new IllegalArgumentException("Unknown mode: " + mode);
  19.     }
  20. }
复制代码
范例安全

错误的使用 Boolean 包装类,有可能会引发空指针异常;
先抛一个问题:包装类 Boolean 有几种“值”?
Boolean 是包含两个值的枚举:Boolean.TRUE 和 Boolean.FALSE;但别忘了,还可以是 null;
一个真实的线上故障,Boolean 在某些情况下被错误地使用,可能会造成空指针异常
例假设你正在修改一个老旧系统的某个方法,这个方法返回 Boolean,有几千行代码:
  1. public static void main(String[] args) {
  2.     if (checkIfMatched("Dummy")){
  3.         System.out.println("matched");
  4.     }
  5. }
  6. /**
  7. * 老旧系统里一个异常复杂的方法,有几千行
  8. * @param str
  9. * @return
  10. */
  11. public static Boolean checkIfMatched(String str) {
  12.     Boolean matched;
  13.     //假设此处存在:复杂处理逻辑,暂时用dummy代替
  14.     if ("Dummy".equals(str)) {
  15.         matched = true;
  16.     } else {
  17.         matched = false;
  18.     }
  19.     return matched;
  20. }
复制代码
现在没问题,但当功能不断迭代后,复杂度也陡然上升,在某个特别的分支里,没有对 Boolean 赋值,至少在编译时是不会报错的:
  1. public static void main(String[] args) {
  2.     if (checkIfMatched("Dummy")) {
  3.         System.out.println("matched");
  4.     }
  5. }
  6. /**
  7.    * 老旧系统里一个异常复杂的方法,有几千行
  8.    *
  9.    * @param str
  10.    * @return
  11.    */
  12. public static Boolean checkIfMatched(String str) {
  13. Boolean matched = null;
  14. //假设此处存在:复杂处理逻辑,暂时用 dummy 代替
  15. if ("Dummy".equals(str)) {
  16.     //模拟:代码在演进的时候,有可能存在 matched 未赋值情况
  17.     if (false) {
  18.         matched = true;
  19.     }
  20. } else {
  21.     matched = false;
  22. }
  23. return matched;
  24. }
复制代码
这个时候,危险寂静而至,还记得上面的问题吗:
包装类 Boolean 有几种“值”?
现在 checkIfMatched() 方法在差别的情况下,方法会返回三个差别的值:true/false/null
这里 null 是非常危险的,如果上游使用如下方式判断条件,考虑下是否有问题?
  1. if (checkIfMatched("Dummy")) {
  2.     System.out.println("matched");
  3. }
复制代码
起首这里不会编译错误,但此处 if 条件处会自动拆箱,对于 null 值会得到 NullPointerException 异常;
小小总结

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

立山

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表