非法状态的出现往往源于用多个独立布尔值而非互斥枚举描述状态
别名: 独立布尔 · 互斥枚举 · flag soup · isLoading
概念解释
isLoading、isError、isSuccess、isEmpty 各是一个可独立翻转的开关。四个开关给出十六种组合,合法的往往只有四五种,剩下的全是非法。互斥枚举把同一事实收成一个值:idle | loading | success | error | empty,组合空间等于节点数。非法状态之所以「往往」出现,不是因为程序员喜欢脏数据,是因为独立布尔是默认的、顺手的建模,空间在声明时就已经超集。
这条点名这种建模习惯。空间一旦超集,非法会出现——那是结构后果,这里要改的是习惯本身。
机制
布尔的诱惑是局部:这一拍要表示「在加载」,就加一个 isLoading。下一拍要表示失败,再加 isError。每个标志在自己的故事里为真,故事与故事之间的互斥没有类型来管。赋值是分别发生的:加载开始时把 loading 置真,成功回调把 success 置真却忘了把 loading 置假,于是 loading ∧ success 诞生。并发下更糟:失败与成功的回调对两个标志各写一次,到达顺序决定哪种非法。
枚举把互斥写进赋值:写成 success 的那一拍,不可能同时还是 loading。这不是风格。它把十六格收成五格,让忘了清标志在类型层无法表示。从布尔迁到枚举时,那些曾经靠「先看哪一个标志」来猜的界面分支会被迫变成穷尽匹配——这正是把隐藏的非法暴露出来的时机。
边界
真正独立的事实应用布尔:通知开关与深色模式互不排斥。把它们收成枚举是另一种错。正交的两台小机器(「同步状态」和「编辑状态」)应是两个枚举,而不是一个十二值的大枚举,也不是六七个布尔。三态(开 / 关 / 不确定)不是两个布尔(checked 与 indeterminate)——那又是经典的非法源,应用一个三值。历史 API 只给布尔时,适配层仍应在内部收成枚举,不要把布尔汤传进界面。有人用位掩码当「省空间的布尔簇」,掩码与独立布尔是同一超集,只是更难读。
怎么落地
- 凡是描述同一对象「现在处于哪一拍」的标志,收成一个枚举。禁止
isLoading与isError并列作为真相来源。 - 界面分支对枚举做穷尽匹配,不写一串
if (isLoading)elseif (isError)的滑梯——滑梯会留下未覆盖的组合。 - 发现两个布尔「理论上不同时为真」,立刻合并,不要加第三条布尔来「再解释一下」。
- 验证:在加载成功的回调里故意不把
isLoading置假(或在枚举版里尝试同时赋两个成员)。布尔版应能渲染出「成功了还在转圈」;枚举版应无法通过编译或在赋值处崩溃。再数产品里所有isXxx标志,两两求「可否同时为真」:答「不应,但类型允许」的每一对都是待收的枚举。