第20部分:SDD反模式
反模式是一种看起来像正确流程的习惯,但会破坏结果。在SDD中,这类错误尤其危险:文件存在,检查存在,代理运行迅速,但人逐渐失去控制。
这部分作为诊断地图。如果流程变得沉重、嘈杂或无用,从这里开始。
代码后写规范
症状:代理先实现功能,然后编写requirements.md、plan.md和validation.md来匹配已完成的代码。
为什么不好:规范不再指导工作,变成了报告。在这种模式下,它无法防止代理做出多余决策。
如何修复:
- 在实现前至少提交规范草稿;
- 禁止代理在创建规范的会话中编写产品代码;
- 在合并请求中明确展示规范提交在实现提交之前。
巨型requirements.md
症状:一个需求文件包含数十个要点、多个场景、未来阶段和有争议的决策。
为什么不好:代理开始选择什么是重要的。人也同样看不到边界。
如何修复:
- 将功能拆分为阶段;
- 将未来想法移到
roadmap.md; - 在
requirements.md中只保留当前分支需要的内容; - 将有争议的决策标记为问题,而非需求。
从未运行过的validation.md
症状:检查中有漂亮的事实,但没有执行过的痕迹。
为什么不好:这种文件制造虚假的完备感。它看起来很严格,但不能让分支进入合并。
如何修复:
- 在每个强制性事实旁边存储命令或手动场景;
- 在代理报告中要求列出通过、失败和未验证的事实;
- 如果无法重现,就不认为事实已确认。
出错后弱化事实
症状:测试失败后,代理修改validation.md中的预期结果,而不是修改代码。
为什么不好:流程开始保护代理的实现,而非产品的意图。
如何修复:
- 要求代理先展示差异,不做修改;
- 特别仔细地审查
validation.md的修改; - 在规范或合并请求中保存修改事实的原因;
- 禁止未经人工决策删除强制性事实。
仪式性的/clear
症状:在阶段之间调用/clear,但之后代理仍然从聊天中获得长篇解释。
为什么不好:该命令制造了可移植性检查的假象。实际上流程仍然依赖人的记忆。
如何修复:
/clear后只给代理文件链接;- 检查新会话是否能从仓库理解下一个任务;
- 如果不能,补充规范,而不是扩展提示词。
技能作为魔法按钮
症状:团队调用Qwen Code技能,但没人阅读SKILL.md,也不理解它做了什么决策。
为什么不好:技能变成隐藏流程。出故障时人们不知道要修什么。
如何修复:
- 将项目技能存储在仓库中;
- 像审查流程代码一样审查
SKILL.md; - 在技能中不仅写命令,还要写限制;
- 在手动流程重复2-3次之前不要创建技能。
QWEN.md作为垃圾堆
症状:在QWEN.md中堆放产品需求、技术栈、个人偏好、临时任务和错误笔记。
为什么不好:代理无法区分永久规则和临时上下文。
如何修复:
- 将产品决策存储在
specs/中;
- 将代理行为规则存储在
QWEN.md中; - 将临时结论移到记忆或回顾中;
- 定期删除过时的规则。
静默修改项目的钩子
症状:钩子格式化、重写或删除文件,而计划中没有明确步骤。
为什么不好:部分变更出现在代理和人控制之外。之后很难理解是谁改变了行为。
如何修复:
- 钩子默认应该检查或记录,而不是修改文件;
- 只允许自动格式化作为明确的命令规则;
- 所有文件修改都应进入普通的
git diff; - 钩子阻止时应解释原因。
记忆作为隐藏的真相来源
症状:代理基于记忆做决策,但这些决策不在specs/、QWEN.md或AGENTS.md中。
为什么不好:新团队成员看不到决策依据。另一个代理也可能得不到它。
如何修复:
- 将记忆视为提示,而非规则;
- 将重复出现的结论移到可审查的文件中;
- 删除过时的记忆;
- 记忆与规范冲突时,选择规范。
无任务的MCP
症状:为项目连接多个MCP服务器"以备将来"。
为什么不好:代理获得多余权限和更多出错方式。团队不再理解哪些外部操作是可能的。
如何修复:
- 只为特定场景连接MCP;
- 限制工具列表;
- 将配置存储在可审查的位置;
- 测试后关闭实验性服务器。
过大的MVP
症状:第一个工作版本包含认证、角色、分析、精美界面、迁移、数据导入和集成。
为什么不好:代理快速创建大量文件,但人来不及理解决策质量。
如何修复:
- 第一阶段应证明一个风险;
- 用时间限制工作;
- 如果分支膨胀,回退到最后一个绿色状态;
- 只有在已有可验证事实后才添加功能。
代理代码中的幻觉
症状:代理自信地引用不存在的函数、方法或包。导入指向不存在的模块;调用使用仅在下个主版本才出现的API;package.json中添加了注册表中不存在的依赖,或名称与知名包几乎相同(例如requests-py而非requests)。
为什么不好:幻觉导入如果代理自己补充了存根,可能在类型检查阶段不报错。不存在的包名尤其危险:攻击者可能提前在注册表中注册此类名称(slopsquatting攻击),npm install会静默拉取恶意代码。
如何修复:
- 在
tech-stack.md中维护允许的依赖列表; - 任何依赖添加都是独立的审查步骤,而非"实现功能"的一部分;
- 首次出现类型或运行时错误时,将包版本与
package.json核对; npm install前肉眼检查包名;与常用名相差一个字母的名称是停止信号;- 如果代理引用代码中之前没有的函数,要求给出链接:"展示定义或说明它应该出现在哪里"。
测试的幻觉
症状:npm test是绿色的,但bug仍然存在。测试存在,但它们没有检查声称的内容。
典型变体:
- 同义反复测试:测试将函数结果与函数中相同的表达式比较——重命名变量会破坏一切,但逻辑从未被检查;
- 镜像测试:测试检查函数返回了它返回的内容,没有独立预期值;
- 快照欺骗:首次运行创建快照,第二次与自身比较;错误被固定在快照中并被当作"正确";
- 无论如何都不失败的测试:唯一断言是函数没有抛出异常。
为什么不好:绿色测试集不再代表完备性。规范形式上完成("覆盖率90%"),但没有任何真正保证。
如何修复:
- 在
validation.md中要求事实重现:修复前失败、修复后通过的命令(尤其针对bug修复,见第11部分); - 审查时阅读测试本身,而非仅看覆盖率数字;
- 对关键位置应用变异测试(Vitest可用Stryker):服务在代码中做微小修改,检查测试是否发现。如果没发现任何变异,测试什么都没检查;
- 禁止业务逻辑使用快照;快照仅允许用于结果为人类可读渲染的场景。
开发者不理解自己的PR
症状:作者打开合并请求,但无法解释为什么采用特定方案;面对审查者的问题,他再次询问代理并转发回答。
为什么不好:代码责任被稀释。半年后团队中没人能说明为什么这里是这种错误处理,这是否是有意识的选择。如果作者不理解自己的PR,审查者无法依赖其判断,未来读者将在假设中迷失。
如何修复:
- 制定规则:PR作者必须在描述中用自己的话一段解释做了什么和为什么(见附录C模板);
- 审查者至少问一个只有人才能回答的问题("为什么这里数据库读取在事务外?");如果作者不回答或回答引用聊天记录,PR退回修改;
- 在团队中鼓励"结对SDD"模式:一人写规范,另一人在实现前审查(第22部分,结对评分);
- 对自己——合并后重读
git diff并大声表述:"我做了X,因为Y"。如果解释不了,下个功能要求代理不仅提供代码,还要在PR评论中提供解释。
诊断检查清单
如果SDD不再有帮助,回答:
- 是否有规范写在代码之后的功能?
- 能否不借助聊天历史执行
validation.md中的事实? - 是否清楚哪些规则在
QWEN.md中,哪些在specs/中? - 是否有无明确步骤就修改文件的钩子?
- 是否有无当前任务的MCP服务器?
- 是否有仅存在于代理记忆中的决策?
- 新代理
/clear后能否根据文件继续工作? - 能否在一分钟内解释项目的当前阶段?
如果三个问题回答为否,不要添加新工具。简化流程。
相关部分
- 第16部分——从审查者角度看同样的错误:如何在他人拉取请求中捕捉反模式。
- 第18部分——同时是安全威胁的反模式(规范中的秘密、无审查的MCP、弱化
validation.md)。 - 第9部分——"功能类型→事实级别"矩阵,直接治疗"弱化
validation.md"反模式。