看不见的,就管不了
回执也好,需求清单也好,都得登记在某个地方。论文把这个地方叫作语境,本书叫它总账:系统当前的状态、每张回执、每份清单,全记在这一本上。
第三个思想就一句话:组件和环境的每一次交互,都必须经过语境。改动要经过它,才会有回执;依赖要经过它,才会被盯着。论文把效应的语境和反效应的语境合成了同一种东西,统一由它居中办理,这就是论文标题里那个「范式」。
反过来,绕开语境做的事,运行时一概不知道。不知道,就谈不上撤,也谈不上通知。论文专门举了一个计数器的例子 p.22,就是下面这个演示。
怎样才算「复原」
第 04 章说,卸掉一个组件,环境回到它来之前的样子。可「一模一样」其实做不到。
论文举的例子很实在:释放一块内存,并不会把内存的排布恢复成分配之前的样子 p.23;删掉一个名字再重新创建,新名字也不是原来那个。物理上,时间回不去。

看不出区别,就算一样
所以论文换了一个更聪明的标准,叫观察等价:两个状态,只要用任何合法操作去试都看不出区别,就算一样。
就像印刷品上的灰色,凑近看是一颗颗网点,退远看就是一片灰。只要读者看到的一样,网点怎么排并不重要。
这个标准还带来一个好处:「看得出什么」由语境里的操作决定。一个格子只开放了哪些操作,别人就只能用这些操作去试。门越窄,越容易做到「看不出区别」。
怎样才算「互不干扰」
几十个组件在同一个系统里同时装装卸卸,凭什么互不干扰?论文的检查办法很省力:按语境里的键,一格一格地看。
不同的格子,天然互不影响:小周的天气插件写「菜单」格,提醒插件写「数据库」格,谁先谁后都一样。真正要小心的只有一种情况:好几个组件在同一个格子里做事。这时候要看这个格子的操作是不是可交换的:谁先谁后,结果看不出区别。
两个窗口手里的空号都是:2 · 5 · 7。小王和小李各来领一个号。
这个例子是论文自己举的 p.29,结论有点反直觉:接口承诺得越少,能随便乱序的操作就越多。不必要的承诺,比如「一定是最小的号」,会把你锁死在某个顺序上。
当然也有天生不可交换的格子。论文点名的是有顺序的中间件链:插在前面的中间件,看到的请求和插在后面的不一样,两者既不能换位,也没法单独撤掉其中一个而不影响另一个 p.29。遇到这种格子,就得老老实实规定顺序。
所以,一扇门换来了什么
把这一章串起来:所有交互走同一扇门,运行时才看得见;看得见,才能撤、能通知;再用「看不出区别」来定义复原,用「一格一格地查交换性」来保证互不干扰。前两个思想的保证,到这里才真正落地。
代价也在这里:门外的东西不受任何保证。所以这一章留给你的,其实是一道检查题。
所有状态都进语境:门外的东西,没有回执,也没人通知。接口少承诺一点,就多一些可以乱序的操作。
请检查这个项目的共享状态: 1. 列出所有模块级变量、单例、全局缓存,标出哪些在模块卸载或重载后会残留; 2. 把它们改成通过一个统一的注册入口管理:每个状态有名字、有清理函数; 3. 对多个模块会同时写的同一份状态,检查写入顺序是否会影响结果;如果会,说明原因,并考虑能否放宽接口承诺(比如不承诺返回值的具体大小或顺序)。
