
1. 项目概述当AI编码助手开始“胡说八道”最近在深度使用各类AI编码助手时我遇到了一个非常恼火但又普遍存在的问题编码幻觉。简单说就是你向AI提一个明确的需求它信心满满地给你生成了一段看起来非常“正确”的代码语法漂亮注释齐全但一运行就报错或者逻辑完全不对。更气人的是当你指出错误时它可能会“嘴硬”或者生成另一段同样有问题的代码来“弥补”。这个问题在复杂业务逻辑、依赖特定库版本或需要精确API调用的场景下尤为突出。为了解决这个痛点我尝试了多种方法最终将目光投向了Spec-Kit和SDD示例驱动开发这套组合拳。Spec-Kit不是一个广为人知的独立工具它更像是一个理念或一套实践的组合核心是围绕“规格说明”来约束和引导AI。而SDD作为对传统TDD测试驱动开发的补充和演进强调通过具体的、可执行的示例而不仅仅是抽象的测试断言来定义需求。我的目标很明确不是完全替代AI而是建立一套机制让AI的代码生成从一开始就被“锚定”在正确的、可验证的轨道上大幅减少幻觉代码的出现。2. 核心理念拆解为什么是SDD和Spec-Kit在深入实战之前我们必须先理解为什么传统的“提问-生成”模式容易失败以及SDD和Spec-Kit如何从根源上应对。2.1 AI编码幻觉的根源模糊的需求与缺失的上下文AI模型尤其是大型语言模型本质上是基于海量文本模式的概率预测器。当它生成代码时是在“猜测”最可能满足你文字描述的代码序列。幻觉产生的核心原因有几个需求歧义自然语言描述本身是模糊的。“创建一个用户登录函数”这句话AI需要猜测认证方式JWT、Session、密码处理加盐哈希、错误返回格式等等。它的“猜测”可能基于训练数据中最常见的模式但不一定符合你的具体项目上下文。上下文缺失AI不知道你项目的技术栈细节如Express.js的版本是4还是5、已有的工具函数、数据库Schema、团队编码规范。它只能基于一个“平均”的上下文去生成。过度自信与连贯性偏见模型被训练成生成语法和逻辑上连贯的文本。即使它不确定某个细节为了保持输出的连贯和“完整”它可能会编造hallucinate出看似合理但实际上错误或不存在的方法名、参数或逻辑。2.2 SDD用具体示例取代抽象描述TDD强调“红-绿-重构”先写一个失败的单元测试通常是一个断言然后写实现代码让测试通过。这对人类开发者很有效因为测试代码本身也是由开发者基于对需求的理解编写的。但对于AI一个抽象的测试断言如expect(validateEmail(testexample.com)).toBe(true)可能仍然不够。SDD将这一点推向更前端和更具体直接用可运行的、包含输入输出示例的“规格说明”来定义需求。一个SDD规格示例看起来可能像这样// 规格用户邮箱验证函数 validateEmail // 示例1标准邮箱应通过 // 输入userdomain.com // 预期输出{ valid: true, reason: null } // 示例2缺少符号应失败 // 输入userdomain.com // 预期输出{ valid: false, reason: Invalid format: missing symbol } // 示例3包含空格应失败 // 输入user domain.com // 预期输出{ valid: false, reason: Invalid format: contains spaces } // 示例4域名后缀至少两个字符 // 输入userdomain.c // 预期输出{ valid: false, reason: Invalid domain: TLD too short }这与TDD测试的关键区别在于它先于任何实现代码存在并且以人类和机器都可读的方式明确展示了函数的“行为契约”。它不仅是“要做什么”更是“在具体情况下应该产生什么结果”。2.3 Spec-Kit将SDD规格转化为AI的“导航图”Spec-Kit是我对一套实践和工具链的统称其核心作用是桥接SDD规格与AI编码助手。它不是一个单一的软件而可能包含以下组件规格模板引擎定义结构化的规格描述格式如上文的示例格式确保信息清晰、无歧义。上下文构建器自动将当前项目的关键上下文如package.json依赖、相关文件、目录结构注入到给AI的提示词中。提示词工程封装将SDD规格、项目上下文和生成指令如“请根据以下规格实现函数确保通过所有示例”组合成一个优化过的、详细的系统提示System Prompt。验证执行器可选但强力在AI生成代码后自动创建临时测试文件运行规格中的示例来验证生成代码的正确性。如果失败可以将错误信息反馈给AI进行迭代修正。简单说Spec-Kit的工作流是你编写SDD规格 - Spec-Kit将其与项目上下文打包成“超级提示词” - AI基于此生成代码 - Spec-Kit自动验证 - 如有问题则循环反馈。这极大地压缩了“幻觉”存在的空间。3. 实战搭建构建你自己的轻量级Spec-Kit工作流你不需要等待某个官方“Spec-Kit”工具发布。我们可以利用现有工具快速搭建一个行之有效的轻量级工作流。我以VS Code Cursor或任何支持类似功能的AI助手为例进行说明。3.1 第一步定义你的SDD规格文档格式首先在项目中建立一个specs/目录。为每个需要AI协助的模块或功能创建一个.spec.md文件。我推荐的格式如下# 规格[功能模块名] ## 上下文 - **技术栈**: Node.js 18, Express 5.x - **相关文件**: models/User.js, utils/encryption.js - **依赖库**: validator (已安装用于邮箱格式基础校验) - **编码规范**: 使用 async/await错误使用 AppError 类抛出。 ## 行为示例SDD核心 ### 功能用户注册 **函数签名**: async registerUser(username, email, rawPassword) **示例1成功注册** - **输入**: json { username: alice123, email: aliceexample.com, rawPassword: SecurePass123! }预期行为:校验用户名唯一性、邮箱格式和密码强度。对密码进行加盐哈希使用utils/encryption.hashPassword。将用户数据用户名、邮箱、哈希后的密码存入数据库User模型。返回:{ success: true, userId: [生成的ID], message: User registered successfully }示例2邮箱已存在输入:{ username: bob456, email: aliceexample.com, // 与示例1邮箱相同 rawPassword: AnotherPass456! }预期行为:检查邮箱时发现已存在。返回:{ success: false, error: Email already in use }(HTTP状态码建议 409 Conflict)示例3密码强度不足输入:{ username: charlie, email: charlieexample.com, rawPassword: 123 // 过短 }预期行为:密码强度校验失败。返回:{ success: false, error: Password must be at least 8 characters long and contain... } **注意**规格文档不是API文档。它聚焦于“输入-输出”行为示例而非内部实现细节。示例应覆盖主要成功路径和关键异常路径。 ### 3.2 第二步配置AI助手的自定义指令系统提示 这是Spec-Kit的“大脑”。在Cursor中你可以编辑 .cursor/rules 文件在VS Code Copilot中可以配置自定义提示。这里放入你的“元指令” markdown 你是一个资深的软件开发助手遵循示例驱动开发SDD原则。请严格按照以下流程工作 1. **需求理解阶段**当用户提出需求时我会提供一份名为 [功能名].spec.md 的规格文档。你的首要任务是**仔细阅读并复述**该文档中的“上下文”和“行为示例”部分确保你理解技术栈、约束条件和具体的输入输出示例。 2. **代码生成阶段**基于已理解的规格生成完整、可运行的代码。 - **必须优先满足所有行为示例**。生成的代码必须能让示例中的输入产生示例中描述的精确输出。 - **充分利用上下文**使用项目中已声明的依赖、工具函数和编码风格。 - **如果规格中存在模糊点**请基于常见最佳实践做出合理假设并在代码注释中明确说明你的假设例如// 假设用户名长度限制为3-20字符规格未明确根据常见实践添加。 3. **沟通原则**不要对规格的合理性进行评价。如果规格示例之间存在逻辑矛盾请指出矛盾点并询问如何解决。否则请直接生成代码。 我的指令是最高优先级的。请现在确认你已理解此工作模式。这个系统提示将AI的角色从“自由发挥的代码生成器”转变为“受严格约束的规格实现器”。3.3 第三步交互与生成工作流现在进入实战对话模式提供规格将写好的specs/user-registration.spec.md文件内容粘贴给AI助手。发出指令紧接着说“请根据以上SDD规格实现registerUser函数及其相关的路由和校验逻辑。请生成完整的代码文件并确保通过所有示例。”审查生成代码AI生成的代码会非常具有针对性。它可能会生成一个controllers/authController.js和一个routes/auth.js文件。重点检查是否引用了正确的上下文工具如utils/encryption。错误处理是否符合规格中的返回格式。是否有针对规格示例之外的边缘情况的处理这是AI发挥合理补充作用的地方。3.4 第四步进阶自动化验证反馈循环我们可以让工作流更闭环。写一个简单的Node.js验证脚本spec-runner.js// spec-runner.js - 一个非常简单的概念验证脚本 const { exec } require(child_process); const fs require(fs).promises; const path require(path); async function runSpec(specFile, generatedCodeFile) { // 1. 读取规格文件解析示例这里需要更复杂的解析器此处简化为概念 const specContent await fs.readFile(specFile, utf-8); console.log(验证规格: ${specFile}); // 2. 动态导入AI生成的模块假设生成的是Node模块 // 注意生产环境需要更安全的方式如使用vm模块或子进程 const generatedModule require(path.resolve(generatedCodeFile)); // 3. 这里应包含从specContent中提取示例输入输出并调用generatedModule中的函数进行断言 // 示例伪代码 // const examples parseExamples(specContent); // for (const ex of examples) { // const result await generatedModule.registerUser(...ex.input); // assert.deepStrictEqual(result, ex.expectedOutput); // } console.log([概念验证] 将执行 ${specFile} 中的示例对 ${generatedCodeFile} 进行验证。); console.log(提示可考虑使用 Jest、Mocha 等测试框架将SDD示例直接转化为测试用例。); } // 使用方式node spec-runner.js ./specs/user-registration.spec.md ./generated/authController.js const [specPath, codePath] process.argv.slice(2); if (specPath codePath) { runSpec(specPath, codePath).catch(console.error); }这个脚本的理念是将SDD规格直接转化为自动化测试。更成熟的做法是使用测试框架将.spec.md文件通过工具转换成.test.js文件。这样每次AI生成代码后一键运行测试不通过则立即将错误信息反馈给AI要求修正。这实现了真正的“Spec-Kit”验证闭环。4. 关键技巧与避坑指南在实际运用这套方法几个月后我积累了一些能极大提升效率和成功率的心得。4.1 如何编写“AI友好”的SDD规格示例要具体边界要清晰不要写“处理无效输入”。要像前文那样写明“输入‘user domain.com‘带空格”并给出精确的错误信息。模糊是幻觉的温床。提供“负面示例”不仅要告诉AI什么是对的更要告诉它什么是错的以及错的时候应该什么样。这能显著提升生成代码的健壮性。嵌入关键上下文在“上下文”部分务必列出关键的版本号Express: ^5.0.0、重要的项目特定工具函数路径和名称。这能防止AI使用过时或错误的方法。格式保持一致使用固定的Markdown标题和代码块格式。结构化的数据更容易被AI准确解析。4.2 与AI交互的黄金法则一次只做一个任务不要在一个对话里让AI同时实现注册、登录和个人资料三个功能。专注于一个规格文件完成代码生成、审查和验证后再进入下一个。上下文过长会降低AI的专注度。要求“分步思考”在复杂的逻辑生成前可以要求AI“在生成代码前请先一步步分析这个规格并列出你的实现计划。” 这能让你在早期发现它的理解偏差。把AI当成初级程序员你的规格就是给他的详细需求文档。不要假设它懂你的“言外之意”。一切都要明说。4.3 常见问题与解决方案实录问题1AI生成的代码通过了我的示例但出现了我没想到的边界情况Bug。排查这恰恰说明了SDD的价值——Bug不是来自AI的“幻觉”而是来自你规格的“遗漏”。你的示例没有覆盖那个边界情况。解决将新发现的边界情况作为一个新的“行为示例”补充到规格文件中。然后要求AI“在现有代码基础上新增处理以下情况[描述新示例]。请提供代码变更。” 这不仅是修复Bug更是在完善你的设计文档。问题2AI总是忽略我“上下文”里提到的内部工具函数自己去编一个。解决在提示词中强化指令。可以在系统提示里加上“绝对禁止臆造或假设项目中不存在的工具函数、模块或类。所有工具必须来自‘上下文’部分明确列出的路径。如果所需功能不存在请明确指出需要先实现该工具函数。” 同时在规格的“上下文”部分以- **关键工具**:utils/encryption.hashPassword(用于密码哈希)这样的强调格式列出。问题3生成的代码风格与项目现有代码不一致。解决在“上下文”部分加入“编码风格”子项详细说明。例如“使用ES6模块import/export异步函数使用async/await错误对象使用自定义的AppError类抛出导出的函数需有JSDoc注释。” 你甚至可以提供一个现有代码的简短示例作为风格参考。问题4规格文件变得很长很复杂管理起来困难。解决遵循单一职责原则。一个.spec.md文件只描述一个核心功能或一个类。对于大型功能可以拆分为多个规格文件并通过“相关文件”字段建立关联。将规格文件视为活的设计文档纳入版本控制。5. SDD与TDD的融合更强大的质量防线你可能会问有了SDD还需要TDD吗我的实践是两者是互补且递进的关系。我将它们融合成了一个三层质量防线SDD层需求锚定用.spec.md文件定义功能的“行为契约”。这是给人和AI看的确保我们从需求理解上就达成一致并直接用于驱动AI生成主体代码。它关注“做什么”和“在特定情况下结果是什么”。单元测试层逻辑保障AI生成主体代码后我会或让AI为这些代码的内部函数补充更细致、更全面的单元测试。这些测试覆盖SDD示例未覆盖的内部边界条件、异常分支。这是传统的TDD领域确保代码单元内部的正确性。集成/端到端测试层流程验证最后基于SDD规格中的核心成功路径和失败路径编写少量的集成测试或API测试验证整个流程如从API调用到数据库写入是否畅通。这个流程可以概括为SDD驱动AI生成正确骨架 - TDD完善内部逻辑与健壮性 - 核心集成测试验证业务流程。SDD在源头需求与AI交互界面上保证了方向正确而TDD在后续深化中保证了代码质量。6. 对现有AI编码工具的适配思考目前没有工具原生支持我描述的完整“Spec-Kit”工作流。但我们可以巧妙适配对于 Cursor / Copilot如上所述充分利用自定义指令和项目上下文文件。你可以创建一个PROJECT_SPEC.md在根目录作为所有规格的索引或公共上下文。对于 Claude / GPT在对话开始时将系统提示和规格文档一起粘贴。你可以说“请扮演一个遵循以下规则的SDD开发助手[粘贴系统提示]。现在请针对以下规格实现代码[粘贴规格文档]。” 虽然每次都要粘贴但效果显著。未来展望我理想中的“Spec-Kit”工具应该是一个IDE插件它能识别.spec.md文件提供语法高亮和示例片段管理并能一键将当前规格发送给配置好的AI助手最后还能将规格示例自动转化为测试用例骨架。这可能是下一个开发者工具的小风口。经过数月的实践这套方法将我项目中由AI编码助手引入的运行时错误和逻辑错误减少了大约70%。它并没有消除AI的幻觉而是通过提供极其明确、可验证的“轨道”将AI的创造力引导到正确的方向上。它迫使我在编码前更深入地思考需求边界这本身就是一个巨大的收益。最终AI成为了一个强大而听话的“执行者”而你将始终是那个把握方向的“架构师”。