你推一下地铁闸机。如果闸机已经锁住,结果是不能通过;如果闸机已经解锁,结果是可以通过。你的动作一样,闸机的规则也一样,变化的是动作发生前的情况。
这就是计算机里“状态”最重要的入口:程序要想让同一个输入产生不同结果,就必须在输入到来时知道对象当前处于什么状态。

先把闸机过程写完整
闸机需要保存一个当前事实:现在是锁住,还是解锁?然后按照规则处理输入:
- 当前是“锁住”,收到“推”:拒绝通过,仍然锁住。
- 当前是“解锁”,收到“推”:允许通过,并可能回到锁住。
同一个输入之所以有两个结果,不是因为“推”这个词有两个含义,而是因为程序在处理输入前读取了不同的当前状态。
当前状态 + 输入 + 规则 → 下一状态这四个部分缺一不可。只有输入,没有当前状态,程序不知道从哪里开始;只有当前状态,没有输入,程序不知道为什么现在要变化;只有规则,没有写回的下一状态,下一次处理仍然会回到旧情况。
状态不是某种特殊格式
把闸机的状态写成一个布尔值很容易,但 true 本身并不自动就是“状态”。它只有放进一个具体的对象和变化过程里,才表示“这台闸机此刻锁住”,并影响下一次收到“推”时的结果。
let locked = true;| 问题 | 它关心什么 | 例子 |
|---|---|---|
| 数据 | 记录了什么内容? | locked: true、一条日志、一条消息 |
| 存储 | 内容放在哪里? | 变量、堆内存、文件、数据库 |
| 状态 | 对象当前是什么,下一步依据什么? | 闸机锁住、订单等待支付 |
同一个值可以同时是数据和状态。数据库里保存 locked: true,它是数据;闸机读取这个值来决定是否放行,它就是闸机状态的一部分。状态不是一种 JSON 格式,也不是只有放在内存里的东西才算状态。
追到代码:状态怎样参与执行
用一个最小函数表示闸机,可以看到状态真正发挥作用的地方:
function pushTurnstile() {
if (locked) {
return { passed: false, message: "请先验证" };
}
locked = true;
return { passed: true, message: "通过" };
}执行顺序不是“调用函数,所以状态自动改变”,而是:
- 函数读取当前的
locked。 - 规则判断它是否为
true。 - 如果锁住,返回拒绝,变量保持原值。
- 如果没有锁住,返回通过,并把
locked写回true。
读取状态和改变状态是两个动作。程序可能读到了正确的值,却没有把变化写回;也可能写回了一个值,但下一次处理的代码根本没有读取它。只有“读取 → 按规则判断 → 写回”连起来,状态才会影响后续行为。
有区分力的对照:如果函数永远直接返回“通过”,无论 locked 是什么,输入结果都不再依赖闸机状态。变量仍然存在,但它已经不参与这条行为规则。
状态一定属于某个边界
问“这个程序有没有状态”太宽。应该问:哪个对象有状态?状态跨多长时间?谁有权修改它?
参数、局部变量和调用栈服务于一次调用;函数返回后,下一次独立调用通常不会自动继承它们。
堆对象、全局变量、连接和队列可以跨多次函数调用存在,但进程退出后通常一起消失。
多个请求通过 sessionId 或 orderId 归到同一条持续工作;标识不自动等于用户身份。
订单、对局、角色和构建任务都有自己的当前事实,权威变更应由业务规则确认。
所以“重启后数据没了”并不神秘:之前的状态只属于已经结束的进程,没有被转移到更长寿命的存储中。网页刷新也不一定意味着工作结束,只要应用用标识找回属于这条工作的历史和当前进度。
状态转移比状态列表更重要
只列出“订单有待支付、已支付、已取消”还不够。真正决定程序行为的是允许怎样转移:
待支付 --支付成功--> 已支付
待支付 --用户取消--> 已取消
已支付 --再次支付--> 拒绝
已取消 --再次支付--> 需要重新创建订单同一个“支付”输入,在“待支付”和“已支付”状态下必须得到不同结果,否则系统可能重复扣款。状态不只是当前标签,还隐含了下一步允许什么、不允许什么。
设计一个状态时至少要明确:
- 当前事实由谁确认;
- 哪些输入可以触发变化;
- 每种当前状态允许哪些变化;
- 变化成功后,新的状态写到哪里;
- 如果执行结果未知,能否安全恢复和重试。
HTTP 无状态,不等于业务没有状态
HTTP 的“无状态”描述的是协议语义:一个请求的理解不应依赖上一个请求的连接或消息。它没有说服务器不能把订单、购物车或任务进度保存到数据库。
请求:cartId + “加入键盘”
↓
读取 cartId 对应的购物车状态
↓
按库存和购物车规则计算变化
↓
保存新购物车状态
↓
返回当前购物车每个 HTTP 请求都可以独立携带 cartId,但业务系统仍然有状态,因为购物车这个对象跨请求保留了会影响下一步的事实。
“请求处理节点无状态”是部署目标:节点不依赖自己私有内存里的唯一真相,换一台节点也能从共享来源继续;“业务系统无状态”则是另一句话,表示订单、购物车或任务没有跨请求的当前事实。前者和后者不能混为一谈。
游戏和 Agent:状态边界会更明显
在多人游戏里,客户端发来的“我要攻击”不是世界已经改变的事实。服务器要读取当前对局状态,检查角色是否还活着、距离是否足够、技能是否可用,再更新权威生命值,并把结果同步给客户端:
当前对局状态 + 玩家操作 + 游戏规则 → 新的对局状态Agent 也一样。模型说“我打算调用退款工具”属于运行中的动作意图;工具真正执行并得到外部系统确认后,结果才可以进入订单或任务的业务状态。模型上下文、工具队列、流式输出属于 Agent 运行时状态;订单是否成功、角色是否受伤属于产品业务状态。两者有关联,但不能互相冒充。
一个可靠的 Agent 服务,通常要让每次请求明确携带或找到:会话标识、本次输入、可见的历史、当前运行状态以及业务系统确认过的事实。它不是让模型凭空记住,而是重新读取状态,组装本次需要的信息,执行动作,再把确认结果写回正确的所有者。
回到开头:同一个“推”为什么一次拒绝、一次通过?因为程序在处理输入时读取了不同的当前状态,并依据同一套规则产生不同的下一步。状态就是某个对象在某个边界内、某个时间点上,会影响下一步行为的当前事实。