学习指南: 第20部分。SDD反模式

模块「第20部分。SDD反模式」中第 2 / 5 节课
您正在未登录状态下查看课程。 请登录,以保存进度并参加测试。

主题:第20部分。SDD反模式

难度级别:中级

预计学习时间:4-6小时(理论2小时,实践2-4小时)

先决条件: SDD(规范驱动开发)基础——课程第1-5部分

理解git、pull requests和基础CI/CD

使用AI助手进行开发的经验(Claude、GPT、Qwen等)

项目结构的基础知识:package.json、requirements、测试

学习目标: 根据第20部分的清单,在实际项目中诊断至少8种SDD反模式

为每种反模式应用具体的修复技术(例如,分离QWEN.md和specs/,在validation.md中引入事实-重现)

审查validation.md并发现测试幻觉(同义反复、镜像、快照欺骗)

创建抗/clear的流程,使新代理无需聊天历史即可继续工作

用自己的话表述PR说明,区分人类和代理的责任

概述:本课程部分是针对变得沉重、嘈杂或无效的SDD流程的诊断图。SDD中的反模式看起来像是正确的流程(文件到位、检查通过、代理工作迅速),但逐渐剥夺了人类对项目的控制。材料涵盖14种具体的反模式:从代码后的规范和巨大的requirements.md到代理代码中的幻觉和测试幻觉。每种反模式都配有症状、危害解释和逐步修复方法。最终的8问题诊断清单可以快速评估流程健康状况:如果有三个否定回答,建议简化而非添加新工具。

核心概念: 代码后的规范:代理先实现功能,然后为现有代码补充requirements.md、plan.md和validation.md的反模式。规范变成了报告而非指导工具。修复方法:在实现前提交粗略规范,禁止在创建规范的会话中编写产品代码,在PR中明确显示规范提交在实现提交之前。

巨大的requirements.md:一个需求文件包含数十个项目、多个场景、未来阶段和有争议的决定。代理开始自行选择优先级,人类失去边界。修复方法:分解为阶段,将未来内容移入roadmap.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服务器"以备将来"。代理获得额外权限,团队不理解可能的外部操作。修复方法:只为具体场景连接,限制工具,将配置存储在可审查位置,检查后关闭实验性服务器。

过大的mvp:第一个版本包含授权、角色、分析、界面、迁移、导入、集成。代理快速创建大量文件,人类来不及评估质量。修复方法:第一阶段证明一个风险,限制时间,扩散时回退到最后绿色状态,只在可验证事实后添加功能。

代理代码中的幻觉:代理自信地引用不存在的函数、方法、包。特别危险的是不存在的包名(slopsquatting攻击:攻击者注册相似名称,npm install拉取恶意代码)。修复方法:tech-stack.md中有允许的依赖列表,添加依赖作为单独的审查步骤,首次错误时核对版本,目视检查包名,引用新函数时要求提供定义链接。

测试幻觉:npm test通过,但bug仍然存在。子类型:同义反复测试(与相同表达式比较)、镜像测试(检查返回内容而无独立预期)、快照欺骗(错误在快照中被固定为"正确")、任何错误都不失败的测试。修复方法:validation.md中的事实-重现,审查时阅读测试本身,变异测试(Stryker for Vitest),禁止业务逻辑使用快照。

开发者不理解自己的pr:PR作者无法解释决定,转发代理的回答。责任模糊,未来维护不可能。修复方法:规则——作者用自己的话解释PR,审查者向人类提问,鼓励"结对SDD",重读git diff并表述"我做了X,因为Y"。

实践练习: 标题:按清单诊断仓库

问题:您获得了一个使用SDD 3个月的项目的仓库访问权限。按照第20部分清单的8个问题进行诊断。对每个否定回答:指出具体反模式,在仓库中找到证据(提交、文件、PR),提出修复建议并举例新状态。仓库包含:200行的requirements.md,带有"阶段2"和"讨论"标记,validation.md中的事实无命令,QWEN.md中有产品决策和个人偏好,pre-commit钩子自动格式化代码,配置中有3个MCP服务器,其中2个未在当前任务中使用。

解答:1. 问题1(代码后的规范):检查git log --oneline -- requirements.md plan.md validation.md —— 文件在实现后提交。反模式:"代码后的规范"。修复:git rebase -i重新排序提交,在CONTRIBUTING.md中规定:规范在实现之前。2. 问题3(QWEN.md vs specs/):在QWEN.md中发现"使用PostgreSQL"(产品性)和"不要在循环中使用await"(行为规则)。反模式:"QWEN.md作为垃圾场"。修复:将"PostgreSQL"移入specs/architecture.md,QWEN.md中只保留代理规则。3. 问题4(钩子):pre-commit在plan.md中无步骤的情况下修改文件。反模式:"静默修改项目的钩子"。修复:替换为检查型钩子,通过明确命令npm run format在CI中进行格式化。4. 问题5(MCP):2个未使用的服务器。反模式:"无任务的MCP"。修复:从配置中删除,附带实验日期和决定的注释。5. 问题7(/clear):检查——创建新会话,只提供文件链接,检查任务理解。如果不理解——补充specs/current-task.md。总计4个否定回答——在添加新工具之前需要简化流程。

难度:中级

标题:审查validation.md发现幻觉

问题:您收到功能"折扣计算"的validation.md和测试。测试通过(覆盖率95%)。找出测试幻觉:(1) test('折扣10%', () => expect(calcDiscount(100, 10)).toBe(100 * 0.9)); (2) test('折扣工作', async () => { const result = await calcDiscount(200, 20); expect(result).toBe(result); }); (3) 价格格式化函数的快照测试; (4) test('不崩溃', () => { expect(() => calcDiscount('abc', 'def')).not.toThrow(); })。对每个:分类幻觉子类型,解释什么bug会被忽略,正确重写测试。

解答:(1) 同义反复测试:100 0.9是函数内部相同的表达式。Bug:如果函数只是返回price (100 - percent) / 100,重命名变量会破坏,但逻辑未被检查。正确:const expected = 90; expect(calcDiscount(100, 10)).toBe(expected); + 单独测试边界值(percent = 0, 100, 101)。(2) 镜像测试:expect(result).toBe(result)永远为true。Bug:任何结果都被认为正确,包括undefined、null、计算错误。正确:严格设定expected = 160; + 类型检查。(3) 快照欺骗:首次运行创建了包含错误的快照。Bug:'1 000,00'与'1 000.00'的格式化被固定为正确且不被检查。正确:明确的expect带locale,禁止业务逻辑使用快照。(4) 任何错误都不失败的测试:唯一断言是不抛出。Bug:函数返回NaN、null、字符串'NaN'——测试都是绿色。正确:单独检查有效输入,检查错误——expect().toThrow()带具体消息。额外:在validation.md中添加事实-重现——修复前失败的命令(例如,calcDiscount(-10, 10)返回负价格)。

难度:中级

标题:将QWEN.md"垃圾场"转换为结构化流程

问题:给定真实项目的QWEN.md(片段):'# QWEN.md\n\n## 产品\n我们在做牙科CRM。主屏幕——预约日历。\n\n## 技术栈\nReact 18, Node 20, PostgreSQL 15.\n\n## 我的偏好\n我受不了循环中的async/await,用Promise.all写。\n\n## 临时\nBug #234:暂时不修,客户同意。\n\n## 3月15日错误\n代理使用了lodash,虽然我们已弃用。不再使用。\n\n## 代理规则\n- 总是先写测试再写代码\n- 不要擅自更改package.json'。按正确用途转换:按目的分离,指出每个块移至何处,哪些规则已过期应删除,哪些需要定期审查。

解答:按第20部分规则结构化:1. "产品" + "技术栈" → specs/product.md和specs/tech-stack.md(产品决策不在QWEN.md中)。2. "我的偏好" → 删除或转换为客观规则:"如果顺序不重要,优先通过Promise.all并行执行独立操作"——作为代理行为规则放入QWEN.md。个人"受不了"不可接受。3. "临时:Bug #234" → 移入回顾或记忆并设过期日期,关闭后2周内从QWEN.md删除。4. "3月15日错误" → 如果规则仍相关("不使用lodash"),移入specs/dependencies.md并附理由;如果代理不再犯——作为过期删除。5. "代理规则"——保留在QWEN.md中,补充:"总是先写测试再写代码" → 明确为"遵循TDD:validation.md中的事实 → 测试 → 实现"。"不要擅自更改package.json" → 强化:"添加依赖——带tech-stack.md审查的单独步骤"。审查:QWEN.md每月审查,specs/——架构变更时,记忆——每周清理。

难度:中级

标题:构建抗/clear的流程

问题:您开发"支付系统集成"功能已2周。聊天历史包含50+条消息,有澄清、计划偏离、妥协。明天新开发者(和新代理)加入项目。创建最小文件集,使/clear后无需从聊天重述即可继续工作。注意:当前阶段——测试webhook,已知问题——sandbox返回202而非200,已做决定——指数退避重试(最初不在规范中)。

解答:创建文件:1. specs/payment-integration/current-phase.md:"阶段3.3:测试webhook。状态:进行中。阻塞:sandbox返回202而非文档所述200。决定:指数退避重试(最多5次,基础延迟1秒,乘数2)。决定日期2024-01-15,原因:支付系统sandbox环境与文档不兼容,生产不受影响。下一步:负载下验证重试。" 2. specs/payment-integration/decisions.md:带上下文、替代方案(等待支付方修复——拒绝,因期限未知)、签名的决定记录。 3. validation.md:更新事实"Webhook处理sandbox的202"带curl重现命令 + 事实"重试总计不超过30秒"带负载命令。 4. QWEN.md:添加规则"与外部API集成时:将文档差异记录在specs/<integration>/discrepancies.md中"。 5. 检查:新会话只获得这些4个文件的链接 + 任务"继续阶段3.3"。如果代理未阅读decisions.md就提议更改重试——流程不稳定,需要强化规范。

难度:高级

案例研究: 标题:MVP崩溃:当代理在48小时内构建过多

场景:EdTech初创公司委托开发在线课程平台。需求:"2周MVP——注册、课程浏览、教师基础分析"。代理(Claude Code,访问大上下文)在48小时内生成:带角色的完整授权(管理员、教师、学生、访客)、字段级权限系统、12个widget的分析仪表板、CSV数据迁移、SendGrid和Stripe集成。全部"工作"——npm test通过,150+文件,80%覆盖率。

挑战:创始人无法解释权限系统如何工作:"代理说这样更安全"。首次真实负载(20个同时注册)发现:email唯一性检查的竞态条件、关键操作缺少事务、"分析"在客户端按全量计算指标。80%覆盖率是幻觉——测试只检查函数存在,不检查正确性。修复一个错误会破坏另外三个,因隐藏依赖。项目6周后完全重写。

解决方案:应用第20部分反模式:1. "过大的MVP"——第一阶段应证明一个风险。本例:能否快速创建并显示课程?其余——后续阶段。2. "测试幻觉"——引入事实-重现:validation.md中每个事实附带修复前失败的命令。变异测试(Stryker)发现60%"覆盖"测试不捕获变异。3. "开发者不理解自己的PR"——引入规则:创始人必须用自己的话解释PR,否则合并被阻止。4. "代码后的规范"——重写从粗略规范"一个课程,一个用户,一个页面"开始。

结果:重写的MVP(实际上是"nano-MVP")——邮箱注册、创建文本课程、浏览列表——3天完成,12个文件。创始人理解每个决定。第二阶段(分析)发现最初12个widget不需要:教师只要求"多少学生开始和完成"。到首个付费用户的总时间从10周缩短到4周,维护成本降低8倍。

经验教训: 代码覆盖率数字——如果测试不检查行为,就是幻觉指标。变异测试对关键路径是必需的。

代理可以创建"工作"的项目,但人类无法维护。人类控制以解释PR的能力衡量,而非代码生成速度。

MVP是验证风险的实验,不是完整产品的迷你版。每个阶段应有一个可衡量的风险和验证事实。

相关概念: 过大的MVP

测试幻觉

开发者不理解自己的PR

代码后的规范

标题:Slopsquatting攻击:当代理幻觉成为漏洞

场景:5人开发团队使用SDD开发文档处理微服务。代理提议PDF解析库——'pdf-parse-pro'而非知名的'pdf-parse'。开发者未目视检查名称,npm install成功,测试通过(代理为缺失方法编写了存根)。包是恶意的:解析超过10MB的文档时发送内容到外部服务器。生产部署3周后发现。

挑战:漏洞出现在两个反模式的交汇处:"代理代码中的幻觉"(不存在的包,名称相似于真实包)和"测试幻觉"(代理存根掩盖了真实功能的缺失)。标准SDD流程未规定单独审查依赖——它们作为"实现功能"的一部分添加。tech-stack.md存在,但不包含允许的依赖列表。

解决方案:按第20部分实施多层防护:1. tech-stack.md——明确的允许依赖列表,含版本、理由和替代方案。2. 添加依赖——单独的审查步骤,类似架构变更。3. 目视检查:与常用名称相差一个字母的名称——停止信号,需要在官方注册表中搜索并检查下载量/年龄。4. 首次类型或运行时错误——必须与package.json核对并检查包是否存在。5. CI中集成Snyk和npm audit,阻止未经审查的新依赖的PR。

结果:实施后6个月:2次添加相似名称的尝试('expresss'而非'express','lodash-es-extra')在CI阶段被阻止。添加合法依赖的时间从0增加到15分钟(讨论必要性)。"影子"依赖减少40%。团队有意识地拒绝了3个"方便"的包,转而使用Node.js内置方案。

经验教训: 代理幻觉——不仅是代码质量错误,更是安全向量。包名需要与秘密和权限同等的严格性。

代理对不存在API的存根创造功能正常的虚假感觉。测试应在实现前失败,否则不是TDD,而是自欺。

"依赖单独审查步骤"的流程在遭遇事件前显得多余。最优审查频率——不是零,而是有意识的。

相关概念: 代理代码中的幻觉

测试幻觉

技能作为魔法按钮

无任务的MCP

标题:"仪式性/clear"后的控制恢复

场景:2人团队(技术主管和产品经理)开发数据可视化工具4个月。流程"工作":每3-4天/clear,然后在聊天中10分钟复述上下文。技术主管休假时产品经理尝试与新代理继续。新会话不理解:哪个阶段活跃、为何选择某种数据格式、接受了哪些妥协。2周用于恢复上下文,项目冻结。

挑战:根本问题:/clear被用作"清理仪式",但流程完全依赖技术主管在聊天中的记忆。没有决定、妥协、计划偏离被记录在可审查文件中。反模式:"仪式性/clear"、"记忆作为隐藏的真相来源"、部分"QWEN.md作为垃圾场"(临时结论积累在代理记忆中)。

解决方案:按第20部分彻底改造:1. /clear后——只有文件链接,没有聊天中的"提醒"。2. 引入specs/decisions/,模板:上下文、考虑的方案、选定决定、原因、日期、签名。3. 每个阶段——单独的specs/phase-N.md,含当前状态、阻塞项、下一步。4. 可移植性检查:每周一名开发者用/clear和新会话+仅文件开始,检查任务理解。5. 代理记忆——定期清理,重复结论迁移到specs/或QWEN.md。

结果:3个月后:新开发者2小时进入项目(阅读specs/decisions/ + 当前阶段),而非2周。技术主管休假不再阻塞项目。意外效果:记录的决定使40%的代理建议被驳回为"已考虑并因X拒绝"。开发速度降低15%(文档时间),但可预测性提高一个数量级。

经验教训: /clear是代理的工具,不是流程。如果新会话从文件而非人类聊天中理解任务,流程才是稳定的。

代理记忆作为提示有价值,但作为规则危险。重复出现的结论必须迁移到可审查文件。

记录决定"减慢"开发,但"加速"团队扩展并降低对特定人员的依赖。

相关概念: 仪式性/clear

记忆作为隐藏的真相来源

QWEN.md作为垃圾场

开发者不理解自己的PR

学习建议: 顺序学习材料,但每个反模式后暂停并检查当前项目——真实情境中的诊断比理论更能巩固

维护"反日记":为实践中发现的每个反模式记录具体文件/提交/PR、修复和一个月后检查日期

与同事结对学习:一人发现反模式,另一人检查是否确实是它,还是误报——培养批判性思维

将最终清单作为每周例行,而非一次性诊断;在日历中设置提醒

视觉型学习者:创建14个反模式的思维导图,它们之间的联系(例如,"仪式性/clear" → "记忆作为真相来源")和您的项目实例

实践型学习者:不要试图同时修复所有反模式;根据清单选择影响最大的3个,一周内实施,然后下一个

听觉型学习者:合并前大声说出每个PR的解释——如果"我做了X,因为Y"说不通,PR就没准备好

额外资源: 课程第16部分(反模式审查):从审查者角度看待相同错误:如何在他人pull-request中发现反模式

课程第18部分(安全):同时是安全威胁的反模式:规范中的秘密、未经审查的MCP、弱化validation.md

课程第9部分(事实矩阵):"功能类型 → 事实级别"矩阵,直接治疗"弱validation.md"反模式

课程第22部分(结对SDD):"一人写规范,另一人在实现前审查"模式——预防"开发者不理解自己的PR"

Stryker mutator(for vitest):发现测试幻觉的变异测试工具——https://stryker-mutator.io/

Npm audit和snyk:自动检查依赖的工具,补充手动包名审查

课程附录c(PR模板):要求用自己的话解释PR的模板

总结:SDD反模式是看起来像正确流程但破坏结果的习惯。与普通错误的关键区别:文件到位、检查通过、代理工作迅速,但人类逐渐失去控制。第20部分提供14种反模式的诊断图:从代码后的规范和巨大的requirements.md到代理代码中的幻觉和测试幻觉。每种反模式有明确的症状、危害解释和具体修复。最终的8问题清单可以快速评估流程健康状况:如果有三个否定回答,规则很简单——不要添加新工具,而是简化流程。核心原则:SDD在代理被规范引导而非替代它时有效;在人类理解自己的PR而非转发代理回答时有效;在/clear后的新会话从文件而非聊天记忆中继续工作时有效。

我的笔记
0 / 10000

笔记保存在当前浏览器中。在其他设备上将不会显示。

课程菜单

课程

基于 Qwen Code CLI 的规范驱动开发
进度 0 / 135