I3.13.2unrepresentable illegal states设计

数据结构若允许表达不属于任何合法状态的组合,就会产生非法状态

别名: 非法状态 · 可表示的非法 · make illegal unrepresentable · 脏组合

概念解释

合法状态是状态机上的节点:例如「未提交 / 提交中 / 成功 / 失败」。数据结构若还能装下「成功且失败」「提交中且已保存且已取消」这种不属于任何节点的组合,这种组合就会在运行中出现。出现不是小概率,是结构允许的必然:只要有赋值、有并发、有反序列化,组合会被写出来。让非法状态不可表示(make illegal states unrepresentable),是把节点集合做成类型能表达的全部,而不是做成文档里的一份愿望。

这条说的是「能装下就会出现」。为什么人们常用一簇布尔把组合空间撑大,是下一刀。

机制

数据的可能取值构成一个空间。状态机的合法节点是这个空间里的一个子集。差集就是非法状态。差集一旦非空,每一条写入路径——用户操作、网络回调、从磁盘读回、另一次乐观赋值——都是一次往差集里掉的机会。防护若靠约定(「这两个字段不要同时为真」),约定在第一条漏掉的赋值处失效。失效之后没有类型错误,只有行为错误:屏幕画出从未设计过的混合物,后续转移在一张没有这个节点的图上找边,又变成未定义转移。

所以非法状态不是「偶尔的脏数据」,是超集建模的产物。建模多出来的那几维,运行时会用满。缩小空间——用一个枚举代替一簇可独立翻转的标志,用互斥的记录体代替可同时填的可选字段——差集变空,非法状态从「靠纪律不要写」变成「写不出来」。

边界

对外协议、数据库历史列、日志里会留下旧形状,反序列化必须面对曾经合法、如今非法的组合。那是边界上的适配,应在入口收成今天的合法节点或明确的「损坏需修复」,不要把旧形状直接喂给现行机器。调试器和内部探针可以看见比界面更宽的空间;那一层不是用户态。有意的超集(草稿对象允许缺字段)要把「不完整」本身做成一个合法节点,而不是用缺字段冒充已提交。分布式里两份合法快照合在一起可能拼出非法——那是合并要升格约束单位的原因,不是允许超集长期存在的理由。

怎么落地

  • 用一张枚举或一组互斥的记录体列出合法节点,让存储和内存里的对象只能是其中之一。
  • 在反序列化、网络解码的入口把未知组合判为错误并停在可修复态,不要「尽量填进现有字段」。
  • 代码评审里看到两个字段的注释是「不要同时为真」,把它们收成一个类型。
  • 验证:对保存对象做一次模糊或手写:同时写上 success=true 与 failed=true,或 progress=100 且 status=pending。若对象能被创建并渲染出画面,空间大于节点集。改成单一枚举后再灌同样的字节,应在入口失败而不是画出「已完成的转圈」。从旧版本文件读入已废弃组合,应进入修复,而不是当成现行节点。

延伸

  • 同组I3.13.1 状态机需要为所有可能的转移路径定义结果,不留未定义转移 · I3.13.3 非法状态的出现往往源于用多个独立布尔值而非互斥枚举描述状态 · I3.13.4 界面呈现的状态应与底层状态机严格对应,不能凭视觉猜测当前状态
  • 相邻I3.11 同步冲突与合并 · I3.09 乐观更新与回滚
  • 站内检索illegal state · unrepresentable · make illegal states unrepresentable

同组卡片

快捷操作

分享

分享当前页面

ios_share

https://hci.top/zh/handbook/I3.13.2