# 当软件的时间开始松动

AI 让实现可以更晚确定，也让使用反馈在原始情境结束前返回。需求、开发、交付和使用仍承担不同责任，却不再必须依次发生。

## Metadata

- HTML: https://glenzli.com/notes/software-time-loosens/
- Markdown: https://glenzli.com/notes/software-time-loosens.md
- Collection: Notes
- Language: zh-CN
- Published: 2026-07-18
- Updated: 2026-07-18
- Tags: ai, software-engineering, runtime, delivery, feedback

## Content

这次旅行中，我临时起意写了一个行程应用。

出发前，我准备了比旅行天数更多的候选安排。真正到了当地，每天还要根据天气、闭馆、预约、体力和临时变化，重新决定第二天去哪里。这个应用最初只是为了把候选行程放在一起，方便随时切换。

它并不是出发前完成的，也没有在第一次部署后完成。

我一边使用，一边发现缺少的能力和原先没有想到的问题。有些修改当天完成，经过检查和重新部署，随后就在同一段行程里继续使用。需求、开发、交付和使用仍然是不同的事情，却不再必须依次发生。

软件在交付后继续维护当然不新鲜。敏捷开发、持续交付和 DevOps 早已让使用反馈进入后续版本。真正让我感到变化的，是这条反馈闭环第一次可以短到赶上软件正在服务的那段现实活动。

过去，旅行中发现的问题通常会被记下来，等事后整理，留到下一个版本，甚至下一次旅行再验证。现在，修改后的版本可能在这次旅行结束前重新回到手里。反馈不必等到情境已经变成回忆以后才产生作用。

我在《[AI 时代的软件开发变迁](https://glenzli.com/notes/software-cognition-in-ai-era)》里讨论过软件的寿命：一个只服务一次迁移、一次整理或一个下午的工具，值得被写出来，却未必值得支付长期维护的成本。

这次旅行让我意识到，寿命并不能决定软件要经历多少次交付。一个只存在两周的应用，也可能在两周里连续变化；一个需要维护十年的系统，反而可能因为风险和迁移成本，很久才完成一次更新。

沿着这条时间线继续往前，还有另一个问题：

> 如果一段实现只服务当前上下文，结果又可以独立验证，它是否还必须在交付之前就成为确定的源码？

## 实现何时被确定

现在让 AI 写一段一次性的数据清洗脚本，已经不是什么困难的事情。通常的做法，是先让它生成代码，跑测试，再放进沙箱或本地环境执行。

这种方式改变了代码的生产成本，却没有改变软件的基本形态。需求先变成源码，源码经过构建和交付，运行时再执行已经确定的实现。即使大部分代码来自 Agent，源码仍然是需求进入运行环境前必须经过的中间产物。

但如果一段过程只处理这一次输入，真实的数据和环境又要到运行时才会出现，具体实现是否可以等到那时再确定？

这里所说的“提前”，是指软件交付或部署之前。运行时仍然要生成或选择候选实现，候选也必须经过检查，真实动作则需要单独授权。变化只在于，我们不再要求所有具体过程都在交付前写好。

运行时决定部分过程并不是新鲜事。JIT 会生成机器码，查询优化器会根据数据分布选择执行计划。它们通常从已经存在的形式表示出发，按照预先定义的语义展开。

AI 带来的可能性更开放一些。系统可以依据已经批准的目的、当下的数据和执行反馈，生成事先没有完整列出的查询、控制流或工具组合。开放性带来了更多选择，也带来了新的不确定性。

税务计算规则需要稳定、复现并持续接受审计，适合提前写成确定性程序。一次季度数据整理则可能不同：字段、缺失值和异常分布每次都在变化。长期保存的可以是指标定义、数据边界和验收规则，具体查询和清洗过程则在看到当期数据以后生成。

这也有一个很实际的限制：只有结果能够被可靠观察和验证的部分，才适合延迟实现。数据是否符合规则，可以由程序检查；一份分析究竟有没有决策价值，最后仍然要由真正使用它的人判断。

## 抽象的账变了

传统软件复用的不只是代码，也是一套统一抽象。

为了让一段实现覆盖更多场景、被更多人维护，我们会提取领域模型，设计通用接口，引入框架和配置系统，再把后来出现的特殊情况收进同一套结构。

后来的人需要先理解这套系统怎样划分概念、组织状态和提供扩展点，然后才能把自己的问题翻译成它能够接受的形式。只要软件存在得足够久、复用范围足够大，这笔学习和维护成本就能被摊销。

运行时生成改变了其中一部分账目。

如果一段过程只服务当前任务，就未必值得为了想象中的复用，提前设计一套覆盖所有情况的抽象。系统可以保留稳定的数据结构、访问边界和验收规则，再按照当前意图生成局部过程。过去常常是人适应软件预先定义的工作方式；以后，更多具体实现可以在边界不变的情况下适应眼前的问题。

抽象不会因此消失。Schema、协议、能力接口、不可变量和验证器反而需要更清楚，因为运行时生成必须以这些东西为边界。

可能减少的，是应用层为了复用短命过程而不断膨胀的通用框架。复杂性也没有凭空消失，它只是从代码库和使用者的学习成本，转移到了意图表达、生成、验证和追溯。若这些环节无法被检查，动态生成只会把看得见的复杂结构换成看不见的复杂行为。

抽象本身也需要接受寿命判断。只有值得长期共享的概念，才值得让所有人共同学习。

## 过程可以临时，边界不能临时

运行时生成不能简化成“给模型一句话，让它自己想办法”。

“给我一份有用的季度报告”只提供了方向，没有说明怎样才算有用，也没有说明执行过程中不能做什么。如果执行者可以在遇到困难时自行修改目标、放宽验收条件或扩大权限，最后完成的就不再是原来的任务。

因此，具体过程可以留到运行时，目标和边界却需要提前说清楚。系统至少要知道允许使用哪些数据和能力，可以承受多少成本，需要留下什么证据，以及谁有权改变这些条件。

运行时可以换计划、换工具，也可以重新生成实现，但不能静默改写自己要完成的事情。目标发生变化时，应当形成新的提案，再由有权决定的人处理。

决定一件事是否值得做、某段实现是否可以进入受控环境、系统是否有权改变现实，以及结果能不能算作完成，本来就是几个不同的问题。一段代码通过测试，不会因此自动获得生产数据的访问权；一次 API 调用返回成功，也不表示真实目标已经完成。

所谓自主，不是让模型获得一切能力，而是让它在已经批准的目标和有限能力之间寻找过程。

## 交付成为检查点

旅行应用仍然采用普通的源码、测试和重新部署。它没有在运行时为自己生成新的产品代码。发生变化的，是使用反馈和下一次交付之间的距离。

这个距离不只是经过了多少小时。需求要经过多少次转述，开发者是否还记得问题发生时的情境，修改需要在多少人和系统之间流转，都会影响反馈能否真正回来。

一个项目可以每天发布，却始终没有解决真实使用中暴露的问题。模型也可以在几秒内生成代码，但在修改经过确认、验证和迁移之前，反馈闭环仍然没有完成。

因此，交付没有消失。它仍然需要确认一个版本在当前范围内可以使用，也需要提供恢复或回退的边界。变化在于，交付不再天然意味着产品形成过程已经结束。

它更像一个经过验证的可用检查点：当前版本可以在这里投入使用，真实使用又会从这里产生下一轮变化。

对个人化、短周期、强依赖情境的软件，这种变化尤其明显。只要修改的成本足够低，反馈就可能在原始活动结束前返回。应用服务的不再只是事先想象出来的旅行，也包括正在发生的这一次旅行。

短寿命因此不再意味着一次性交付。一个应用可以只存在几天，却在这几天里连续到达多个可用状态。活动结束以后，它仍然可以被冻结、归档或删除；为了让它在过程中持续可用而完成过多次交付，并不自动把它变成长期资产。

## 两条循环

使用中的变化和运行时生成很容易被混在一起，但它们并不是同一件事。

系统为了处理当前数据，临时生成一条查询或一段清洗代码，属于一次任务内部的执行。只要目标、权限和验收条件没有变化，它仍然处在同一个执行边界里。

如果真实使用暴露出应用无法表达某类计划，或者原来的数据结构、验收方式和能力边界已经不够用，就需要修改产品本身，并形成新的可用版本。

前者以当前结果、失败或中止结束；后者要回到产品层，重新处理代码、契约、状态迁移和交付。

两条循环可以连接。某段运行时生成的实现被反复使用以后，可能成为下一次产品修改的候选；一次使用中暴露的问题，也可能促使系统重新判断哪些过程应该提前实现，哪些可以继续留到运行时。

但它们不能被合并。系统在当前任务里重新规划，不等于产品已经产生新版本；产品进入下一次交付，也不意味着它必须采用运行时生成。

更快的反馈并不自动导向更动态的软件，运行时生成也不代表软件会自己演化。它们只是分别改变了软件时间线上的不同位置。

## 代码什么时候成为资产

运行时生成的代码不必只有“提交进仓库”和“彻底消失”两种结局。

只服务本次执行的实现，可以在结果通过验证、必要证据得到保留后丢弃。可能有限复用的实现，可以连同它适用的目标、输入和环境一起缓存；每次复用前，再重新检查这些条件是否仍然成立。

当一段实现被频繁使用，开始产生外部依赖，需要人工维护，或者失败代价明显上升时，才值得判断它是否应该成为长期代码。

确认以后，它应当被整理或重写，进入设计、审查、测试和发布流程，而不是把一次运行中偶然生成的代码原样写回仓库。

过去，我们往往在开发前猜测什么值得复用。以后，一部分代码可以先在真实运行中证明自己的适用范围和责任，再决定是否进入代码库。

代码库也因此更接近长期责任的集合，而不是系统曾经找到过的所有解法。

不过，运行时产物的晋升只是产品变化的一种来源。用户反馈、稳定缺陷、环境变化和新的使用方式，同样可以独立推动下一次交付。软件的演化不能被缩减成“把模型写得不错的代码保存下来”。

## 软件有不止一种时间

回头看，我现在会把几个问题分开。

一项软件责任值得维持多久，是寿命问题。具体实现必须在什么时候确定，是实现绑定的问题。真实使用距离下一个可用版本有多远，则是反馈的问题。

它们彼此有关，却不能互相推出。

短寿命软件可以在几天里多次发布；长期系统也可能因为风险很高而缓慢变化。一段实现只运行一次，不代表它适合在运行时生成；采用运行时生成的系统，也不一定比普通应用更快地改变产品本身。

这些区别之所以重要，是因为我们过去习惯把它们压进同一条时间线：先定义需求，再设计和编码，然后交付、使用，最后收集反馈。每一环都要等前一环基本结束，才有条件开始。

软件开发从来没有真的如此整齐，只是理解、修改、验证和交付的成本，让它常常不得不表现得像这样。

AI 降低了一部分成本以后，原本绑在一起的时间开始松动。具体过程可以晚一点确定，使用反馈可以早一点回来，一段代码也可以在证明自己值得维护以后再成为资产。

责任仍然需要找到明确的落点，只是不必再要求所有东西同时固定下来。

## 仍然需要长期维护的东西

我设想的并不是一套完全动态的软件。

权限检查、状态机、关键规则、协议、验证器和恢复机制，仍然适合由长期维护的确定性程序承担。路径难以预先穷举、强依赖当前上下文、寿命很短而结果可以验证的过程，才适合留给运行时生成。

两种区域会长期存在于同一个系统里。

它们也有不同的寿命。一条规则可能维护多年，一段临时实现只存在几分钟，执行证据却可能因为追溯需要保留更久。源码也就不足以单独描述一次执行：还需要知道当时批准了什么，系统具有什么能力，依据什么证据接受了结果。

Git commit 仍然重要，但软件的版本边界已经开始延伸到代码之外。

这条路线也有明确的范围。硬实时控制、安全关键系统、结果难以验证或副作用无法恢复的任务，仍然更适合提前写成确定性程序。一段逻辑如果稳定、高频而且长期被依赖，提前实现通常也更简单。

同样，反馈回来得快，不代表可以把生产环境当作测试场。开发与使用可以在时间上重叠，新的版本仍然应当在隔离环境完成必要检查，再迁移到下一个可用状态。变化越靠近真实使用，验证、状态迁移和回退越不能省略。

AI 对软件工程更深的影响，也许不是把代码写得更快，而是重新安排软件里的时间：哪些责任必须提前固定，哪些实现可以等上下文出现以后再生成，哪些反馈还能赶在原来的事情结束前回来。

需求、开发、交付和使用仍然承担不同责任，只是不再必须按顺序发生。

实现可以晚一点，责任不能。

## 相关材料

- [AI Software Engineering Notes](https://gitlab.com/glenzli/ai-software-engineering-notes)：本文所依据的软件寿命、Agentic 工程与意图导向执行笔记。
- [In-Use Evolution](https://gitlab.com/glenzli/ai-software-engineering-notes/-/blob/main/foundations/IN_USE_EVOLUTION.md)：真实使用如何在原始情境仍然有效时形成下一可用检查点。
- [Milestone II: Intent-Oriented Execution](https://gitlab.com/glenzli/ai-software-engineering-notes/-/blob/main/milestone-2-intent-oriented-execution/README.md)：关于实现绑定时机、意图契约、运行时生成与产物晋升的研究假设。
- [行程扭蛋 / Plan Gacha](https://gitlab.com/glenzli/plan-gacha)：旅行途中持续使用和修改的行程计划工具。
