状态机:从数学家的思想实验到驱动世界的隐形齿轮
序章:一个被误解的概念
“状态机?听起来像是某个工科宅男在键盘前拍脑袋发明的术语。”
这是绝大多数人第一次听到”状态机”时的反应。它确实自带一种”专业壁垒”的气质,让人觉得高深莫测、遥不可及。但事实上,状态机可能是计算机科学里最被高估其复杂度、最被低估其普遍性的概念之一。
它诞生于数学家的纸笔之间,壮大于计算机的电路之中,最终溢出到所有需要描述”状态如何随时间变化”的领域。今天,它驱动着你手机里的每一行代码、电商平台上的每一个订单、高速公路上每一辆汽车的发动机控制单元——而你对此浑然不觉。
这篇文章,就是要揭掉它身上那层神秘的面纱,把它的来龙去脉完整地讲给你听。
一、起源:1936年,一个叫图灵的年轻人
要讲状态机的源头,我们必须把时间拨回到1936年。
那一年,英国数学家艾伦·图灵只有24岁。他正在思考一个当时数学界最根本的难题:”到底什么是’可计算’的?“换句话说,有没有一种通用的机器,能把人类用纸笔进行的所有数学推导,都自动化地执行出来?
这个问题在今天看来或许是”纯理论”的,但在当时,它直接关系到数学的根基——如果有些问题是”不可计算”的,那数学家们穷尽一生可能都在追逐一个不存在的答案。
图灵的回答震惊了世界。他提出了一种假想的机器,后世称之为”图灵机”。
这台机器极其简单,却蕴含着深刻的力量。它由四个部分组成:
- 一条无限长的纸带,被划分成一个个格子,每个格子上写着符号(比如”0″或”1″)。
- 一个读写头,可以读取当前格子里的符号,也可以在格子上写入新符号,或者向左/向右移动一格。
- 一套内部状态,比如”正在寻找第一个数字””正在做加法””计算已完成”。这些状态的数量是有限的。
- 一张规则表,这是整个机器的灵魂。每条规则规定了:在某个状态下,读到某个符号,就执行某个动作(写入/移动/停止),并转入下一个状态。
你看,这不就是今天我们说的状态机吗?
图灵机本质上就是一台超级状态机。 图灵用这个极简的数学模型证明了一个划时代的结论:只要状态和规则是有限的,这台机器就能执行任何人类能用算法描述的运算。这就是著名的”图灵完备性”——今天你手机里的芯片、电脑里的CPU、云数据中心的服务器群,本质上都是图灵机的工程实现。
所以,状态机不是计算机的”产物”,而是计算机的”蓝图”。 计算机是按照状态机的逻辑被造出来的,而不是反过来。就像建筑图纸先于大楼存在一样,状态机作为数学思想,早于任何一台实体计算机而存在。
这是一个关键的认识转折:当你理解了状态机的数学出身,你就不会再把它看作某种”编程技巧”或”设计模式”——它是一种关于计算的底层逻辑,和你用哪种编程语言、跑在哪台机器上,没有本质关系。
二、演化:从数学走进工程
图灵机在理论上大获全胜,但它有一个”致命”的问题:纸带是无限长的。在现实世界里,没有无限的内存,也没有无限的时间。图灵机是一个思想实验,用来证明”理论上能算”,但它没法直接造出来。
于是,工程师们把图灵机做了一次务实的”阉割”:把无限纸带砍成有限存储,把任意复杂的规则压缩到有限状态。这一刀砍下去,诞生了一个更具实操性的概念——有限状态机(Finite State Machine,FSM)。
到了1950年代,状态机开始真正走出数学论文,进入现实世界。
- 1956年,两位贝尔实验室的科学家——爱德华·摩尔(Edward Moore)和乔治·米利(George Mealy)——分别提出了两种经典的状态机数学模型。摩尔的模型里,输出只取决于当前状态;米利的模型里,输出取决于当前状态和输入事件。这两套模型至今仍是每个计算机系本科生的必修课。
- 1960年代,状态机被用来设计早期的电话交换系统。你拿起听筒拨一个号码,交换机就从”空闲”跳到”拨号音”,再跳到”振铃”,再跳到”通话”,最后挂断回到”空闲”。每一步都有严格的状态约束——你不可能在”挂断”状态下发出”接通”的指令。
- 1970到80年代,随着微处理器的普及,状态机成了嵌入式系统的”不二法门”。一台洗衣机的控制芯片、一个微波炉的面板按键、一辆汽车发动机的燃油喷射控制——这些设备的内存只有几十到几百个字节,根本跑不了复杂的操作系统,但一个轻巧的状态机就能让它们可靠地工作几十年。
与此同时,计算机科学家们又发现:状态机不仅能”做事情”,还能”检查事情”。他们发明了”状态图”(Statechart),用状态机来描述复杂系统的行为规范。比如说,你设计的电路到底对不对?写一套状态机模型,用工具自动验证它是否在任何输入下都不会进入”死锁”或”非法状态”——这就是今天芯片设计行业赖以生存的”形式化验证”技术的雏形。
从这个时候起,状态机就已经分裂成了两条并行发展的道路:
- 执行路径:把状态机当作程序逻辑,直接跑在硬件或软件上,控制系统的实际行为。
- 描述路径:把状态机当作规范语言,用来建模、分析、验证一个系统的设计是否正确。
这两条路互相滋养,一直延续到今天。
三、扎根:为什么编程语言离不开它
到了1990年代,面向对象编程大行其道,状态机的实现方式也随之升级。
早期的C语言程序员用 switch-case 写状态机,状态一多就变成”意大利面条式代码”。后来,软件工程界发现可以用”状态模式“——这是GoF设计模式中的一种——把每个状态封装成一个类,用多态来处理状态切换。这比 switch 优雅得多,但代码量也大了不少。
进入21世纪,随着图形化建模工具的成熟(比如MATLAB的Stateflow、UML状态图)和代码生成技术的普及,状态机的开发模式再次发生了一次质变:工程师可以在画布上拖拽出状态和转移箭头,工具自动生成C/C++代码。这种”以模型驱动开发”的方式,在汽车、航空、工业自动化等高安全领域已经成为绝对的主流。
为什么状态机能如此持久地”霸占”程序员的工具箱?原因其实很简单:
人类的大脑不擅长追踪复杂的时序逻辑,但状态机擅长。
当你试图理解一个系统”在什么条件下能做什么、不能做什么”时,时序逻辑会把你的脑子搅成一团浆糊——”如果先发生A再发生B,但如果中间插进来一个C,那就……”——这种推理方式很快就超过了人脑的短期记忆容量。
而状态机给了你一个降维打击的工具:它把时间维度的混乱逻辑,压缩成了一张二维的表格——行是状态,列是事件,格子里是动作和下一状态。这张表一铺开,所有非法操作无所遁形,所有状态迁移一目了然。
这就是为什么你会在编译器(词法分析阶段用状态机识别关键字和标识符)、网络协议(TCP的三次握手和四次挥手是经典状态机)、游戏AI(怪物从”巡逻”到”追击”到”攻击”的切换)、图形界面(按钮的”启用/禁用”、对话框的”打开/关闭”)中,一次又一次地遇到它。
状态机成了程序员工具箱里一把”万能扳手”——不是因为它花哨,而是因为它可靠。
四、C++11中的状态机:现代C++的优雅实现
当状态机遇到C++11,事情变得有趣起来。C++11引入了一大波新特性——enum class、lambda表达式、std::function、std::map的初始化列表——这让实现状态机从”苦差事”变成了”赏心悦事”。
我们用一个经典的门禁系统(旋转闸机)来完整展示,在C++11中如何构建一个工业级可用的状态机框架。
门禁系统的业务规则
这是一个地铁站入口的旋转闸机:
- 有两种状态:锁定(Locked)和解锁(Unlocked)。
- 有两种事件:投币(Coin)和推门通过(Pass)。
- 锁定状态下投币 → 解锁,放行通过。
- 解锁状态下通过 → 锁定,恢复戒备。
- 锁定状态下硬推 → 报警,状态不变。
- 解锁状态下再投币 → 无效操作,状态不变(防止重复投币)。
C++11完整实现
#include <iostream>
#include <map>
#include <functional>
#include <string>
// 一、定义状态:用 enum class 保证强类型作用域
enum class State {
Locked,
Unlocked
};
// 二、定义事件:同样用 enum class
enum class Event {
Coin,
Pass
};
// 三、定义"动作"的类型别名:一个无参无返回值的函数
using Action = std::function<void()>;
// 四、状态机核心类
class Turnstile {
public:
Turnstile() : current_state_(State::Locked) {
// 初始化状态转移表:键是(当前状态, 事件),值是"要执行的动作"
// C++11 的初始化列表 + pair + lambda,让代码极其紧凑
transition_table_ = {
// 规则1:锁定 + 投币 → 解锁,并打印提示
{{State::Locked, Event::Coin}, [this]() {
std::cout << " 叮当!投币成功,闸门解锁,请通过" << std::endl;
current_state_ = State::Unlocked;
}},
// 规则2:解锁 + 通过 → 锁定,并打印提示
{{State::Unlocked, Event::Pass}, [this]() {
std::cout << " 咔嚓!已通过,闸门重新锁定" << std::endl;
current_state_ = State::Locked;
}},
// 规则3:锁定 + 硬推 → 报警,状态不变
{{State::Locked, Event::Pass}, [this]() {
std::cout << " *** 警报!检测到非法闯入!***" << std::endl;
// 注意:不改变 current_state_
}},
// 规则4:解锁 + 投币 → 无效,提示但状态不变
{{State::Unlocked, Event::Coin}, [this]() {
std::cout << " 提示:闸门已解锁,无需重复投币" << std::endl;
// 状态不变
}}
};
}
// 对外接口:触发事件
void HandleEvent(Event evt) {
auto key = std::make_pair(current_state_, evt);
auto it = transition_table_.find(key);
if (it != transition_table_.end()) {
// 执行对应的动作(lambda)
it->second();
} else {
// 理论上不会走到这里,因为我们在表中覆盖了所有组合
std::cout << " 错误:未定义的状态转移" << std::endl;
}
}
// 辅助方法:查看当前状态(用于调试)
std::string GetStateName() const {
return (current_state_ == State::Locked) ? "锁定" : "解锁";
}
private:
State current_state_;
// 核心数据结构:二维状态转移表
// 键:(State, Event) 的pair
// 值:Action(即 lambda 函数)
std::map<std::pair<State, Event>, Action> transition_table_;
};
// 五、测试代码
int main() {
Turnstile turnstile;
std::cout << "=== 旋转闸机状态机演示 ===" << std::endl;
std::cout << "初始状态:" << turnstile.GetStateName() << std::endl;
std::cout << std::endl;
std::cout << "1. 投币:" << std::endl;
turnstile.HandleEvent(Event::Coin);
std::cout << " 当前状态:" << turnstile.GetStateName() << std::endl;
std::cout << std::endl;
std::cout << "2. 再次投币(重复操作):" << std::endl;
turnstile.HandleEvent(Event::Coin);
std::cout << " 当前状态:" << turnstile.GetStateName() << std::endl;
std::cout << std::endl;
std::cout << "3. 推门通过:" << std::endl;
turnstile.HandleEvent(Event::Pass);
std::cout << " 当前状态:" << turnstile.GetStateName() << std::endl;
std::cout << std::endl;
std::cout << "4. 再次推门(非法闯入):" << std::endl;
turnstile.HandleEvent(Event::Pass);
std::cout << " 当前状态:" << turnstile.GetStateName() << std::endl;
return 0;
}
这段代码的亮点
第一,enum class强类型枚举。 传统的C++98 enum会把枚举值暴露到外层作用域,容易命名冲突。C++11的 enum class 必须写成 State::Locked 和 Event::Coin,编译器能区分这两种类型——你不可能不小心把事件当状态用,安全性和可读性都大幅提升。
第二,std::map + std::pair 作为二维转移表。 键是 {当前状态, 事件} 的组合,值是动作函数。查表就是 map.find(key),复杂度对数级。如果要改成哈希表获得常数级查找,只需把 std::map 换成 std::unordered_map,C++11同样支持。
第三,std::function + lambda 把”动作”和”转移”打包在一起。 每个规则里,你看到的就是业务逻辑本身——std::cout打印、current_state_赋值——一目了然。没有 switch-case 的散落,没有函数指针的类型转换麻烦,所有逻辑集中在初始化列表里,后期要增加新状态或新事件,只需要在 transition_table_ 里添加一行。
第四,[this]捕获让lambda能修改成员变量。 这是C++11 lambda的核心能力——每个动作闭包都持有 this 指针,所以能读写 current_state_,但又保持封装性。
运行输出
=== 旋转闸机状态机演示 ===
初始状态:锁定
1. 投币:
叮当!投币成功,闸门解锁,请通过
当前状态:解锁
2. 再次投币(重复操作):
提示:闸门已解锁,无需重复投币
当前状态:解锁
3. 推门通过:
咔嚓!已通过,闸门重新锁定
当前状态:锁定
4. 再次推门(非法闯入):
*** 警报!检测到非法闯入!***
当前状态:锁定
如何扩展
假设业务新增一个”管理员强制复位”事件(AdminReset),不管当前什么状态,直接回到”锁定”。你需要做的仅仅是:
- 在
Event枚举中增加AdminReset。 - 在
transition_table_的初始化列表里增加两条(或一条通配规则):
{{State::Locked, Event::AdminReset}, [this]() {
std::cout << " 管理员复位,保持锁定" << std::endl;
// 已经是锁定,不变
}},
{{State::Unlocked, Event::AdminReset}, [this]() {
std::cout << " 管理员复位,强制锁定" << std::endl;
current_state_ = State::Locked;
}}
不需要改动任何其他代码,这就是状态机在工程中最大的魅力——状态和逻辑的增删改,被限制在一张表格之内,不会像意大利面条一样散落各处。
五、转折:当状态机遇见”事件”
前文说到,状态机在嵌入式系统和芯片设计里扎根了几十年。但到了21世纪的头十年,互联网开始疯狂生长,程序员们突然发现了一个尴尬的事实:
状态机在”单机”上活得很好,但一旦面对分布式网络、高并发请求、微服务之间的消息传递,它就有点”水土不服”了。
为什么?
传统的有限状态机是”拉模式“的——你得主动调用一个 HandleEvent() 函数,把事件塞进去,状态机同步地处理完再返回。但在互联网后端,成千上万的请求从四面八方涌来,你没法”等”着事件一个一个处理,而是必须随时被事件唤醒,而且同一个时刻可能有几十个事件在并行流动。
于是,状态机脱下它”数学公式”的外衣,换上了一身新行头,重新登场。这次它的名字叫:事件驱动架构(Event-Driven Architecture)。
你仔细看,事件驱动架构的底层逻辑,和经典状态机一模一样:
- 当前状态:订单是”待支付”还是”已发货”?
- 输入事件:用户点击了”支付”?还是物流系统推送了”已签收”?
- 输出动作:发送一封邮件?调用一次库存扣减API?记录一条日志?
- 下一状态:”待支付”变成”已支付”,”已发货”变成”已完成”。
骨架没变,但实现方式彻底变了——不再是一个 switch-case 函数在单线程里同步运行,而是变成了一条异步的事件流,横跨多个服务、多台机器、甚至多个数据中心。
这个转变带来了三个革命性的变化:
第一,状态不再”存”在内存里,而是”存”在数据库里。传统嵌入式状态机的状态是内存里的一个枚举变量,断电就丢。但互联网订单状态机,状态必须持久化在关系型数据库或NoSQL里——因为一个订单可能要活好几天甚至几个月,中间服务器可能重启无数次,状态不能丢。
第二,事件不再”同步”处理,而是”异步”流转。用户支付成功,订单状态机发出一个”支付成功”事件,这个事件可能同时触发:物流系统开始备货、积分系统加积分、消息系统发短信、数据分析系统更新报表。所有这些动作不是顺序执行的,而是并行响应的——谁订阅了这个事件,谁就在自己的节奏里处理它。
第三,状态机变成了”工作流引擎”。当状态变多、规则变复杂时,就诞生了专门管理状态流转的中间件——比如 Amazon Step Functions(云原生工作流)、Apache Flink(流处理中的状态管理)、Camunda / Activiti(开源工作流引擎)。这些工具本质上就是企业级的状态机运行平台:你只需要用YAML或BPMN图把”状态和转移规则”画出来,平台负责保证——不管系统怎么重启、网络怎么抖动、消息怎么重复——每个事件都恰好被处理一次,每个状态转移都原子且正确。
六、实战:一个订单状态机的”解剖课”
为了让你真切感受到”从嵌入式到互联网”的跃迁,我们解剖一个具体的例子——电商订单状态机。
在80年代,如果你通过邮购目录打电话下单,订单状态简单得像一条直线:已下单 -> 已发货 -> 已完成。三个状态,两条转移线,搞定。
但今天的电商订单状态机,复杂程度已经天差地别。一个典型的订单状态流转如下:

这个状态机里,每个状态都有严格的”允许事件”规则:
- “待支付”状态下,只允许”支付”和”取消”事件。如果你调用”发货”接口,系统直接返回错误——这就是状态机在保护业务数据不被污染,防止”还未付款就发货”的低级错误。
- “运输中”状态下,如果用户发起退货,系统触发”退货申请”事件,状态跳到”退货中”,而绝不能直接跳到”已完成”。因为退货需要经过审核、收货、验货等一系列流程。
- “已完成”状态下,根据业务规则,可能允许在7天内发起退货,也可能不允许。这个规则如果变了,只需要修改状态机的转移表,不需要改动其他任何代码。
这背后的工程实现,早已不是一行 if-else 能承载的了。
在互联网大厂的架构里,这个订单状态机通常是这样运转的:
- 状态定义写在公司的”领域模型”里,用Java enum或Protobuf定义,所有微服务共享同一份定义,保证大家对”什么是已支付”的理解完全一致。
- 状态转移规则配置在一个”规则引擎”里,业务人员可以在后台管理界面上修改——比如”允许已支付状态下取消订单,但要扣5%手续费”,改了之后实时生效,不需要重新编译和发版。
- 事件生产与消费通过消息队列(如Kafka或RabbitMQ)传递。订单服务支付成功后,往Kafka里发一条
OrderPaid事件,物流服务、积分服务、数据分析服务各自订阅这个主题,各自做自己该做的事,互不阻塞。 - 状态持久化和幂等性靠数据库事务加乐观锁保证。每次状态更新都带版本号(version字段),更新时检查版本号是否匹配,防止并发冲突导致状态错乱。
七、更深一层:状态机与”最终一致性”
讲到分布式系统,就不得不提一个关键问题:状态机如何在网络不稳定的情况下保证正确性?
这是一个非常现实的问题。假设订单从”待支付”变成”已支付”,需要同时做两件事:更新订单状态为”已支付”,调用库存服务扣减库存。如果库存服务刚好挂了,扣减失败,但订单状态已经改成”已支付”了——钱已经扣了,库存却没减,这属于严重的数据不一致。
怎么解决?业界演化出了一套经典方案:Saga模式(长事务补偿)。
Saga本质上是一个带回滚功能的状态机:
- 正向流程:
待支付 -> 已支付(扣库存)-> 待发货 -> 已发货 - 如果”扣库存”这一步失败,就执行补偿动作:把订单状态从”已支付”回退到”待支付”,并自动发起退款。
你看,每个状态转移都配了一个”反向转移”(即补偿操作),保证出错时可以撤回。这就是状态机在分布式世界里的”自救”机制。
再往上一层,像Apache Flink这样的流处理框架,把状态机嵌入到了事件时间(Event Time)的处理逻辑中——它不按你代码被调用的顺序来处理事件,而是按事件本身发生的时间戳来处理。即使机器宕机重启,状态也能从”检查点”(Checkpoint)恢复,不丢不重。
这就是状态机在现代大数据架构中的形态:分布式、持久化、容错、实时。 它依然叫状态机,但它的肉身已经彻底改头换面。
八、回顾:一条完整的时间线
让我们把整个故事串起来,你就能看到一条跨越近百年的清晰脉络:
| 年代 | 形态 | 载体 | 核心挑战 |
|---|---|---|---|
| 1936 | 数学思想(图灵机) | 纸笔 | 定义”计算”的本质 |
| 1950s | 数学模型(Moore/Mealy) | 学术论文 | 形式化描述状态机的各类变体 |
| 1960s-80s | 硬件实现 | 电话交换机、单片机 | 用最少的资源做最多的事 |
| 1990s | 软件设计模式 | C++/Java代码 | 如何优雅地管理复杂状态 |
| 2000s | 图形化建模+代码生成 | Stateflow/UML工具 | 让非程序员也能设计逻辑 |
| 2010s | 分布式事件驱动架构 | Kafka+数据库 | 状态持久化、异步解耦、容错恢复 |
| 2020s | 云原生工作流+流处理 | Flink/Step Functions | 实时计算、弹性伸缩、最终一致性 |
每一个阶段,状态机的”骨架”都没变——依然是”状态+事件+转移”的三元组——但它的”肉身”却随着基础设施的演进而不断重生。
从单条纸带上的读写头,到继电器阵列,到硅芯片上的晶体管,到云端的容器集群——状态机的”舞台”换了无数次,但”剧本”始终如出一辙。
九、当代:状态机早已超越计算机
走到今天,状态机已经远远不是一个”计算机概念”了。它像一束光,照进了多个完全不同的领域。
- 在工业界,状态机是安全标准的硬性要求。以汽车行业为例,ISO 26262功能安全标准明确规定:任何涉及安全相关的软件模块,必须用状态机或类似的形式化方法进行设计和验证。为什么?因为只有状态机能保证”在刹车状态下,绝对不能响应油门加速指令”——这种确定性的保障,是安全认证的前提。
- 在学术界,状态机演化出了众多变体。时间自动机加入了时钟约束,可以建模”必须在3秒内响应”这类实时要求;概率状态机的转移带概率权重,用于建模不确定环境;分层状态机允许状态嵌套——”开机”状态下包含”待机”和”运行”两个子状态,关闭总电源时不管子状态在哪儿,直接切到”关机”,极大简化了复杂系统。
- 在人工智能领域,强化学习里的”马尔可夫决策过程”本质上就是一个带奖励的概率状态机——智能体在每个状态下采取某个动作,以一定概率转移到下一状态并获得奖励。甚至今天的大语言模型,在生成每个token时,内部也在维护一个高维的”隐状态”——虽然它复杂到难以解释,但底层”状态+输入→输出+下一状态”的逻辑骨架,从未改变。
- 在计算机之外,生物学家用状态机模拟基因调控网络,研究基因如何在不同条件下被”打开”或”关闭”;经济学家用它建模市场周期的切换——繁荣期、衰退期、复苏期之间的转移;语言学家用它分析自然语言的语法结构,比如一个句子如何从”名词短语”状态转移到”动词短语”状态。
状态机从数学家的纸笔出发,经历近一个世纪的演化,已经成为一种跨学科的通用语言——任何需要描述”事物如何随时间和事件而变化”的场景,都可以用状态机的框架来理解和建模。
十、尾声:为什么它永远不死
写到这里,你可能会问:现在有了AI,有了大模型,有了神经网络,状态机这种”死板的规则表”会不会被淘汰?
我的看法恰恰相反。
大模型擅长的是模糊推理——写一首诗、总结一段话、识别一张图片里的物体。但当你面对的是支付系统、火箭发射控制器、手术机器人、核电站监控系统,你绝不允许”模糊”。你不希望AI”猜测”一下这笔钱该不该扣,或者”大概”觉得发动机温度还安全。
状态机提供的确定性——”在A状态下,只有B事件能被响应,其他任何输入要么忽略要么报错”——是这些生命攸关的系统安全运行的底线。人类花了几十年才建立起来的可靠性标准,不会因为AI的兴起就轻易放弃。
事实上,最新的趋势是把两者的优势结合起来:
- 用大模型去理解用户的自然语言意图——”我想取消这个订单””帮我改一下收货地址””这个商品怎么还没发货”——把它翻译成标准的、结构化的”事件”(比如
CancelOrder、UpdateAddress、QueryLogistics)。 - 然后把这个事件交给状态机去原子地、确定地、可追溯地执行状态转移。状态机不负责”理解”,只负责”执行”。
大模型负责”听懂人话”,状态机负责”动手干活”。一个管模糊,一个管精确,各司其职,相得益彰。
所以,状态机不是”过时的老古董”,而是计算机科学里少数几个在数学上被证明正确、在工程上被反复验证可靠的基础模型。它像欧几里得几何一样,从几条公理出发,建构了一座庞大而自洽的逻辑大厦。只要这个世界仍然依赖”状态+事件+转移”这三要素来运转,状态机就不会消失。
结语
故事讲到这里,从1936年图灵在剑桥的办公室里写下第一行数学公式,到2026年的今天,一条跨越九十年的逻辑之线,终于收束到了你屏幕上的这行文字里。
状态机看似抽象,实则无处不在。你下次再看一个电商订单的物流轨迹、一台电梯的楼层指示、一个游戏角色的行为切换、甚至你自己从”饿了”到”吃饭”再到”饱了”的日常流程,大概都会多一分”原来你也在这里”的亲切感。
它从来不是什么高深莫测的东西。它就是世界运转的一种底层语法——简洁、确定、可靠。
这大概就是为什么,九十年过去了,我们还在用它。