非法状态是我们绝不想让系统进入的状态。不期望状态是我们不想持续停留的状态。许多我们希望是非法的状态,实际上只是不期望状态。
以一个日历软件为例,它将日历事件存储为 {user: {events: [event]}},其中每个事件都有开始和结束时间。这允许一个人同时参加两个事件。我们可能认为这是非法的,并将数据类型替换为 {user: {time: optional event}},从而使这种情况不可能发生。然而,日程冲突并非非法,只是不期望!一个人确实可能报名参加了两个时间重叠的事件。也许他们应该选择一个事件,也许他们稍后再决定去参加哪个,也许其中一个事件实际上并不代表线下会议。
在这种情况下,停留在不期望状态虽然不理想,但也是可以接受的。其他一些不期望状态如果不及时退出,就会导致无效状态。如果预订的乘客数量超过可用座位数,航班就处于不期望状态。这必须在乘客实际登机之前解决,因为"飞机上的实际乘客数量超过可用座位数"是一种非法状态。
在某些情况下,不期望状态不会导致非法状态,但长期停留在不期望状态仍然是一个问题。我们可能保证网络分区永远不会导致数据不一致。尽管网络分区这一不期望状态不会引发数据损坏这一非法状态,但如果我们始终不修复分区,仍然会面临严重问题。
一般来说,如果我们无法完全控制系统的行为,不期望状态就可能发生。我们无法保证网络绝对可靠、服务器始终在线、用户输入的数据全部正确。如果系统接收来自外部世界的输入,外部世界就可能将我们推入不期望状态。我们需要能够检测到这些状态,以便加以解决。
即使我们完全控制系统,我们可能仍然希望系统能够暂时进入不期望状态。如果愿意的话,航空公司本可以使超售航班成为不可能。但航空公司希望能够超售,因为他们预计会有一定数量的乘客不登机。我们需要允许他们进入不期望状态,然后建立机制防止其演变为非法状态。
有时我们需要不期望状态才能使某些工作流程成为可能。也许95%的用户永远不希望接受冲突事件,阻止这种不期望状态会改善他们的体验。但如果没有这种不期望状态,有意重复预订自己就无法实现。有些人可能需要这样做!在这些情况下,我们需要非常明确地告知用户他们正在进入不期望状态,然后让他们自行决定如何退出。
(航空公司和用户想要的是不期望状态!是我们工程师认为它"不期望",因为它可能导致后续问题。)
非法状态对应于被违反的不变量。按照惯例,我们将其写为 []!Illegal:Illegal 在任何时候都不应为真。如果某个状态满足了 Illegal,那么我们的系统就存在一个缺陷。
不期望状态更为棘手,因为它们可能同时是安全性和活性属性。如果对航空系统进行建模,我们不一定需要检查超售的属性,只需检查不会发生超员航班即可。1 在验证过程中,我们可能会发现超售是导致超员航班的主要原因,和/或如果超售问题不解决,最终必然会出现超员航班。此外,如果"我们从不超售"能保证"我们从不出现超员航班",那么我们会说"无超售"是一个比"无超员航班"更强的属性。
"我们永远不会持续处于网络分区状态"被形式化为 []<>!Partition:我们可以进入分区状态并长时间保持分区,但最终必须修复分区。P 规范语言将这些称为热状态。
(附:如果一切顺利,下周应该会有新一期《程序员的逻辑学》发布!)