图片来源:unsplash
使用布尔标志值来管理代码库中的状态机可能听起来是个好主意,但事实并非如此。布尔值可能是许多程序员接触的第一个数据类型。它非常简单,只有两种状态:true 和false。
随着代码的发展,它很容易产生代码复杂性、可读性和可扩展性方面的问题。
通常,标记参数会划分函数的逻辑,迫使函数根据值执行多个操作。这可能会导致业务逻辑混乱,并且代码库很容易得到以下树结构:
背景故事
下面的故事清楚地展示了状态机和函数参数中布尔参数的弱点。
一群软件开发人员曾经构建了一个模块来管理用户状态。其中一位开发人员坚持使用布尔值,因为该模块只需要两种状态:ONLINE 和OFFLINE,这看起来快速、简单、直接。尽管大多数人并不完全同意这个建议,但他们还是照办了。
最终,如下所示的函数开始填充代码库:
func setUserState(isUserOnline :Bool)
不久之后,团队里来了一位新成员,他想知道下面这句话的真正含义:
setUserState(true) //新人一直盯着这个。
虽然有人想出了一个看起来更好的函数名称(setUserOnline),但是一旦出现新的业务需求(包括另一个用户状态:BLOCKED),事情就变成了一场噩梦。让我们看看这些开发人员为解决这个问题做了什么。
三态布尔问题
布尔值通常表示两种状态,但在某些语言中(例如使用布尔对象的Java),可以使用null来分配第三种状态。在上下文中,BLOCKED 将设置为null。
图片来源:unsplash
虽然这似乎不需要额外的布尔值来适应新的用户状态,但它很容易导致NullPointerExceptions。
此外,在不同情况下区分false 和null 可能很棘手。例如,当布尔属性game.isPlaying 为true 时,它清楚地表明游戏处于播放模式。但是当它为false 或null 时, false 表示游戏暂停或停止。
正如您所看到的, false 没有提供足够的信息来轻松识别和回忆其绑定状态,而三态布尔值只会使逻辑变得复杂。
此外,当系统需求包含另一个称为EXPIRED 的状态时会发生什么?由于现在有四个状态,这种方法无法解决。让我们看看大多数开发人员采用的另一种方法。
多个布尔值带来隐藏的依赖项
开发人员最终扩展了之前函数的签名,为新状态添加了两个布尔参数:
func setUserState(isUserOnline : Bool, isUserBlocked : Bool, isUserExpired : Bool)
看似满足业务需求的简单扩展被迫在代码库中引入隐藏的依赖项和大量新组合。
创建的两个隐藏依赖项是isUserOnline — isUserExpired 和isUserOnline — isUserBlocked。现在需要显式管理额外状态以避免状态冲突。例如,被阻止/过期的用户无法在线。以下是要处理的两种冲突状态的示例:
#条件1: isUserOnline:false 和isUserExpired: true#条件2: isUserOnline: false 和isUserBlocked: true
添加的状态越多,函数很容易变成一长串参数。事情变得不可持续,因为你最终会得到很多, || ,以及其他复杂的分支逻辑来处理互斥和相关布尔值。
布尔值具有类型安全性和可读性问题
使用多个布尔值,很可能将它们混合在一起,最终可能会传递错误的值(可能来自其他对象),并且编译器甚至不会做出反应。在重构和执行代码审查时,这可能是一场噩梦,需要编写大量单元测试来解决此类问题。
图片来源:unsplash
此外,很容易忘记布尔变量的false 或true 值的真正含义,并且理解充满布尔值的函数调用(如下所示)只会变得非常困难:
设置用户状态(真,假,假)
有人可能会说,现在许多编程语言都支持命名参数,可以提高函数的可读性。但话又说回来,有可能意外地传递了反向或不正确的布尔值,但函数签名仍然匹配。
这个故事中的软件开发人员如果使用枚举而不是布尔值,就可以避免这些问题。
选用枚举,避免用布尔值
枚举数是一种数据类型,由一组可以以类型安全方式使用的命名值组成。虽然它可能看起来不像布尔值那么简单,但使用枚举或其他用户定义类型有助于避免设置具有多个分支的复杂if 语句。
枚举UserStates{case activecase inactivecaseBlockedcase 已过期}
1. 枚举清晰、简洁
枚举强制对所有状态进行命名,这使得理解它们的含义并创建自记录代码变得容易。同样,枚举清楚地表明这些值是相互排斥的,消除了对冲突状态的任何怀疑。
在函数中将枚举作为参数传递更干净,有助于避免神秘的布尔值。只需比较以下两行:
setUserState(true,false, false)//下面的版本更加简洁清晰。setUserState(UserStates.active)
2. 枚举是类型安全的
对于枚举,您不能为其分配指定值以外的任何值,因为它是类型安全的,因此不可能意外交换值或传递无效状态,因为它可以被编译器发现。
并非所有语言都支持本机枚举,在这种情况下您可以创建自定义类型。例如,在JavaScript 中,可以通过“冻结”对象中的常量来解决这个问题:
constUserState={ ACTIVE: 1, INACTIVE: 2, BLOCKED: 3, EXPIRED: 4 }; Object.freeze(UserState);
3. 枚举使扩展和重构更容易
扩展枚举器中的值集更容易,因为与布尔值不同,可能的状态组合数量在每个新情况下不会加倍。
此外,许多编译器足够聪明,可以指示需要进行哪些更改才能适应新的枚举。例如,Swift 会抛出错误,而在其他语言中,很容易查明枚举中发生的所有情况。
用额外的新案例扩展已经存在的枚举是毫不费力的,因为数据类型保持不变,使得重构整个事情变得更容易。
图片来源:unsplash
当然,布尔值也不是没有用的。如果您确定状态是二元且互斥的,或者方法名称已经描述了状态(例如setEnabled(true)),则可以随意使用布尔值。
但通常情况下,需求会发生变化并且需要添加新的状态。因此值得尝试二元素枚举,它比布尔标志更安全。枚举有助于让您的代码面向未来,而无需跟踪布尔字段。
不要为了简单而滥用布尔值。枚举是一个不错的选择。
用户评论
念安я
布尔参数真的很鸡肋,枚举类型简直是救星!
有10位网友表示赞同!
凉话刺骨
一直被布尔参数搞得头疼,这篇简直是醍醐灌顶啊!
有19位网友表示赞同!
爱情的过失
终于找到一个替代布尔参数的好方法了,枚举类型简直太棒了!
有5位网友表示赞同!
情深至命
原来枚举类型还可以这样用,涨姿势了!
有20位网友表示赞同!
心脏偷懒
布尔参数的局限性太大了,枚举类型才是王道!
有20位网友表示赞同!
安之若素
强烈推荐枚举类型,代码可读性提升不止一点点!
有19位网友表示赞同!
冷嘲热讽i
布尔参数的弊端,枚举类型可以完美解决!
有12位网友表示赞同!
青瓷清茶倾城歌
布尔参数太单调,枚举类型带来了更多的可能性!
有16位网友表示赞同!
ˉ夨落旳尐孩。
枚举类型让代码更清晰,更容易理解!
有7位网友表示赞同!
该用户已上天
使用枚举类型后,代码不再那么晦涩难懂了!
有18位网友表示赞同!
熏染
布尔参数的缺点?枚举类型轻松解决!
有5位网友表示赞同!
雪花ミ飞舞
终于找到一个比布尔参数更好的方法了,枚举类型简直是神器!
有15位网友表示赞同!
晨与橙与城
枚举类型让代码变得更优雅,更易维护!
有15位网友表示赞同!
花花世界总是那么虚伪﹌
枚举类型不仅功能强大,而且易于理解和使用!
有7位网友表示赞同!
蹂躏少女
布尔参数太死板,枚举类型让代码更灵活!
有13位网友表示赞同!
夜晟洛
枚举类型可以清晰地表达参数的含义,告别布尔参数的混乱!
有7位网友表示赞同!
孤败
布尔参数的时代已经过去了,现在是枚举类型的天下!
有6位网友表示赞同!
没过试用期的爱~
终于找到一个替代布尔参数的完美方案了,枚举类型就是答案!
有13位网友表示赞同!
把孤独喂饱
布尔参数的缺点让人头疼,枚举类型是最好的选择!
有7位网友表示赞同!
怅惘
枚举类型让代码更加简洁,易于阅读和理解!
有15位网友表示赞同!