💡阅读指南

第一版开发完成以后,真实问题往往才开始出现。发现问题并不可怕,马上修改也不一定错;真正需要警惕的是,Agent 在运行产品的过程中直接改变代码,导致已经完成的版本失去清楚的边界。本节通过一次真实的微信文章入库,讨论开发、验收和正式运行之间应该怎样隔开。

1.1 第一版完成以后,问题才真正出现

自动化测试可以检查已经想到的情况,却很难覆盖真实世界里的所有变化。网页可能临时增加验证,第三方接口可能返回一种没有见过的数据结构,用户也可能用一种需求文档里没有写到的方式操作系统。胶囊系统完成第一版以后,我们拿一篇真实的微信公众号文章做入库测试,很快就遇到了这种情况。

这很正常。第一版的意义不是从此不再出现问题,而是形成一个可以使用、可以观察、也可以继续修改的起点。很多问题只有把系统交给真实资料、真实环境和真实操作以后才会暴露出来。人工测试的价值,也正在这里。

但问题出现以后怎样处理,决定了这个项目是在继续开发,还是在慢慢失去控制。

1.2 一次入库测试改变了已经完成的代码

这次测试的输入是一篇微信公众号文章。胶囊系统第一次请求网页时,微信返回了“环境异常,完成验证后即可继续访问”,提取器却把这段提示误认为文章正文。如果继续处理,证据库里保存的就不是原文,而是一段没有意义的拦截提示。

Agent 随即检查了请求差异,发现原来的请求标识触发了微信验证页。它把网页提取器改成浏览器兼容的请求标识,补充测试,然后重新发起入库。正文取到了,但文章标题又退化成了 mp.weixin.qq.com。Agent 再次修改标题解析,让系统读取微信页面中的 og:title。最后,文章成功进入证据库,还生成了一个主题页和一个概念页。

如果只看结果,这次处理似乎很顺利。问题被发现,代码被修正,资料也成功入库。但实际发生的流程是这样的:

Text
用户提交真实资料
        ↓
产品运行失败
        ↓
Agent 直接修改产品代码
        ↓
运行局部测试
        ↓
继续处理刚才的真实资料
        ↓
宣布入库成功

运行产品和开发产品混在了一起。用户原本只是要求系统保存一篇文章,并没有要求 Agent 改造网页提取器。更重要的是,胶囊系统此前已经生成实施报告,并记录全量测试 50 passed。代码改变以后,这份报告对应的已经是修改前的版本。新代码只运行了网页提取相关的局部测试,不能继续沿用原来的全量测试结论。

胶囊系统项目当时还没有建立 Git 仓库,这使问题更加明显。两次代码修改没有提交记录,无法准确回答改了什么,也不能方便地回到入库以前的版本。代码虽然能运行,但版本已经失去了可追踪性。

所以,这次修改在技术上有依据,在工程过程上却不完整。

1.3 边测试边改并不总是错误

开发人员一边运行程序、一边修改代码,是日常开发中最普通的工作方式。正在编写一个模块时,运行测试发现错误,马上修改,再执行测试,没有必要先开会、写报告或者发布一个新版本。探索性开发本来就需要快速反馈。

真正需要区分的是,我们此时面对的究竟是哪一个环境。

所处阶段 发现问题后的处理
开发环境 可以立即修改代码,运行相关测试,继续调试
验收环境 先记录缺陷并停止本轮验收,修复完成后使用新的候选版本重新验收
正式环境 不在用户任务中临时改代码;问题进入缺陷或紧急修复流程

同一个动作放在不同阶段,含义完全不同。在开发环境里修改提取器,是调试;在验收过程中修改以后继续沿用原来的测试结论,会让验收对象发生变化;在正式运行时由 Agent 自行改代码,风险更大,因为系统下一次处理资料时,执行的已经不是用户原来使用的那个版本。

我们这次使用的是专门的飞书验收空间,目的本来就是用真实资料暴露问题。因此,发现微信提取缺陷完全符合预期。更合适的做法是在问题出现时结束这次验收,把它转成一条缺陷记录,然后回到开发环境修复。修复后的代码通过完整测试,再作为新的候选版本进入验收空间。

1.4 版本基线固定一次可以核对的完成状态

所谓版本基线,就是在某个时刻把 SPEC、Plan、代码、测试和实施报告对应起来。它并不意味着代码以后不能修改,而是规定任何人都不能在没有记录的情况下改变这个对应关系。

Text
SPEC + Plan
     ↓
代码 + 测试
     ↓
实施报告
     ↓
V1 基线

建立基线以后,实施报告中的测试数量、需求完成情况和已知问题才有明确对象。以后发现缺陷,可以从这个版本出发创建一次小修改;如果修改失败,也能回到原来的状态。没有基线,“第一版完成了”只是一句话,我们无法证明当前目录中的代码是不是当时验收的那一份。

新问题出现以后,也不需要把所有材料推倒重来。软件工程保护的是变更过程,不是阻止变更。一次受控的修改通常会经过下面这条路径:

Text
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 和开发失败了。相反,真实资料让我们看到了自动化测试没有覆盖的部分。接下来的工作也不是重新开发一套胶囊系统,而是把这些问题逐一变成有记录、有测试、有版本的修改,让第一版继续演化。