第一版开发完成以后,真实问题往往才开始出现。发现问题并不可怕,马上修改也不一定错;真正需要警惕的是,Agent 在运行产品的过程中直接改变代码,导致已经完成的版本失去清楚的边界。本节通过一次真实的微信文章入库,讨论开发、验收和正式运行之间应该怎样隔开。
1.1 第一版完成以后,问题才真正出现
自动化测试可以检查已经想到的情况,却很难覆盖真实世界里的所有变化。网页可能临时增加验证,第三方接口可能返回一种没有见过的数据结构,用户也可能用一种需求文档里没有写到的方式操作系统。胶囊系统完成第一版以后,我们拿一篇真实的微信公众号文章做入库测试,很快就遇到了这种情况。
这很正常。第一版的意义不是从此不再出现问题,而是形成一个可以使用、可以观察、也可以继续修改的起点。很多问题只有把系统交给真实资料、真实环境和真实操作以后才会暴露出来。人工测试的价值,也正在这里。
但问题出现以后怎样处理,决定了这个项目是在继续开发,还是在慢慢失去控制。
1.2 一次入库测试改变了已经完成的代码
这次测试的输入是一篇微信公众号文章。胶囊系统第一次请求网页时,微信返回了“环境异常,完成验证后即可继续访问”,提取器却把这段提示误认为文章正文。如果继续处理,证据库里保存的就不是原文,而是一段没有意义的拦截提示。
Agent 随即检查了请求差异,发现原来的请求标识触发了微信验证页。它把网页提取器改成浏览器兼容的请求标识,补充测试,然后重新发起入库。正文取到了,但文章标题又退化成了 mp.weixin.qq.com。Agent 再次修改标题解析,让系统读取微信页面中的 og:title。最后,文章成功进入证据库,还生成了一个主题页和一个概念页。
如果只看结果,这次处理似乎很顺利。问题被发现,代码被修正,资料也成功入库。但实际发生的流程是这样的:
用户提交真实资料
↓
产品运行失败
↓
Agent 直接修改产品代码
↓
运行局部测试
↓
继续处理刚才的真实资料
↓
宣布入库成功
运行产品和开发产品混在了一起。用户原本只是要求系统保存一篇文章,并没有要求 Agent 改造网页提取器。更重要的是,胶囊系统此前已经生成实施报告,并记录全量测试 50 passed。代码改变以后,这份报告对应的已经是修改前的版本。新代码只运行了网页提取相关的局部测试,不能继续沿用原来的全量测试结论。
胶囊系统项目当时还没有建立 Git 仓库,这使问题更加明显。两次代码修改没有提交记录,无法准确回答改了什么,也不能方便地回到入库以前的版本。代码虽然能运行,但版本已经失去了可追踪性。
所以,这次修改在技术上有依据,在工程过程上却不完整。
1.3 边测试边改并不总是错误
开发人员一边运行程序、一边修改代码,是日常开发中最普通的工作方式。正在编写一个模块时,运行测试发现错误,马上修改,再执行测试,没有必要先开会、写报告或者发布一个新版本。探索性开发本来就需要快速反馈。
真正需要区分的是,我们此时面对的究竟是哪一个环境。
| 所处阶段 | 发现问题后的处理 |
|---|---|
| 开发环境 | 可以立即修改代码,运行相关测试,继续调试 |
| 验收环境 | 先记录缺陷并停止本轮验收,修复完成后使用新的候选版本重新验收 |
| 正式环境 | 不在用户任务中临时改代码;问题进入缺陷或紧急修复流程 |
同一个动作放在不同阶段,含义完全不同。在开发环境里修改提取器,是调试;在验收过程中修改以后继续沿用原来的测试结论,会让验收对象发生变化;在正式运行时由 Agent 自行改代码,风险更大,因为系统下一次处理资料时,执行的已经不是用户原来使用的那个版本。
我们这次使用的是专门的飞书验收空间,目的本来就是用真实资料暴露问题。因此,发现微信提取缺陷完全符合预期。更合适的做法是在问题出现时结束这次验收,把它转成一条缺陷记录,然后回到开发环境修复。修复后的代码通过完整测试,再作为新的候选版本进入验收空间。
1.4 版本基线固定一次可以核对的完成状态
所谓版本基线,就是在某个时刻把 SPEC、Plan、代码、测试和实施报告对应起来。它并不意味着代码以后不能修改,而是规定任何人都不能在没有记录的情况下改变这个对应关系。
SPEC + Plan
↓
代码 + 测试
↓
实施报告
↓
V1 基线
建立基线以后,实施报告中的测试数量、需求完成情况和已知问题才有明确对象。以后发现缺陷,可以从这个版本出发创建一次小修改;如果修改失败,也能回到原来的状态。没有基线,“第一版完成了”只是一句话,我们无法证明当前目录中的代码是不是当时验收的那一份。
新问题出现以后,也不需要把所有材料推倒重来。软件工程保护的是变更过程,不是阻止变更。一次受控的修改通常会经过下面这条路径:
V1 基线
↓
记录问题与复现方式
↓
判断问题属于缺陷、需求遗漏还是新功能
↓
修改相应的 SPEC、Plan、代码或测试
↓
运行局部测试和全量回归测试
↓
形成 V1.0.1 或新的候选版本
↓
重新进行人工验收
这条流程不要求攒够一批问题以后再一次性重写。改动越大,越难判断究竟是哪一处造成了新的错误。更常见的方式是把问题拆成可以独立验证的小修改,每次修改都留下记录,再根据发布节奏决定把哪些修改合并进同一个版本。
1.5 不同问题从不同位置重新进入 SDD
人工测试发现的问题,并不都要从需求简报重新开始。需要回到哪一层,取决于问题改变了什么。
| 问题类型 | 判断依据 | 重新进入的位置 |
|---|---|---|
| 实现缺陷 | SPEC 已经规定正确行为,代码没有做到 | 增加缺陷样例,修改代码和测试 |
| 需求遗漏 | 真实场景没有被 SPEC 覆盖,系统应该怎样处理还没有结论 | 补充需求或 SPEC,再调整 Plan |
| 技术方案问题 | 需求没有变化,但当前技术无法可靠满足 | 修改 research 或 Plan,再实现 |
| 新功能 | 用户希望系统承担原来范围之外的职责 | 建立新的需求变更和 SPEC |
微信公众号的验证页属于实现缺陷。现有规则已经要求:网页正文无法可靠读取时,系统应当进入审核,不能把错误页面当成证据。因此,这个问题不需要重新编写整份需求简报,只需要补充验证页样例、修正提取器并完成回归测试。
微信文章标题保存在 og:title 中,也属于网页兼容性缺陷。系统本来就需要为衍生证据生成可以辨认的标题,代码只是没有覆盖这种页面结构。
但如果我们进一步提出“胶囊系统必须长期适配微信公众号的各种验证策略,必要时调用登录浏览器读取文章”,问题就变了。这会增加新的访问方式、权限风险和失败边界,需要先修改 SPEC 和技术计划,不能继续当作一个顺手完成的小修复。
1.6 Agent 应该先报告问题,再切换到开发任务
SE 项目把 Agent 作为系统主控,但这不代表 Agent 可以在产品运行时自行改变产品。运行态 Skill 和开发态 Agent 应该有明确边界。
以后再遇到类似情况,运行态 Agent 应当停止当前资料处理,保留输入、错误结果和复现条件,然后向用户提交一份缺陷说明。只有用户明确决定进入开发任务以后,Codex 才修改代码、补充测试和形成新的版本。
运行态发现问题:记录并报告,不修改产品代码。
开发态处理问题:依据缺陷记录修改代码,完成测试并形成新版本。
这个限制看起来多了一步,实际是在保护用户。用户要求“把文章收入知识库”时,系统行为应该保持稳定;用户要求“修复微信文章无法入库的问题”时,Agent 才获得修改产品的任务。把这两个意图分开以后,资料处理结果、代码变更和测试报告才能分别核对。
第一版出现问题并不说明前面的 SPEC、Plan 和开发失败了。相反,真实资料让我们看到了自动化测试没有覆盖的部分。接下来的工作也不是重新开发一套胶囊系统,而是把这些问题逐一变成有记录、有测试、有版本的修改,让第一版继续演化。