
先看一个不少人在踩的场景需求下来之后打开 AI 编程工具把需求原封不动贴进对话框AI 生成一段看起来完整、注释齐全的代码。复制到项目里编译通过功能好像也能跑于是直接提交。结果上线第二天用户下单时填了超出预期的商品数量金额计算出现偏差又过了两天项目里多了一个根本没人维护的第三方依赖部署脚本直接报错。问题排查到半夜最后只能回滚版本。这不是 AI 编程工具本身不好用而是使用方式出了问题。围绕“别瞎用 AI 写代码”这个主题本文会把工程师级 AI 编程开发工作流拆开来讲从需求澄清、任务拆分、上下文准备到 AI 生成、人工审查、测试验证再到提交维护每一环都给出可以落地的做法。文中的示例项目基于 TypeScript 与 Vitest重点演示如何让 AI 从“代码生成器”变成“协作者”而不是一个帮你挖坑的“自动补全机器”。1. 为什么“瞎用 AI 写代码”会翻车1.1 AI 编程工具的能力边界AI 编程工具本质上是基于大语言模型的代码生成系统。它通过分析海量开源代码与文档学习“看起来合理的代码长什么样”然后根据你的上下文预测下一段代码。这种机制决定了它的两个特性第一生成速度快覆盖语言多第二它并不真正理解程序在运行时的状态也不理解你项目的业务规则。很多初学者把 AI 当成了“免费高级工程师”觉得只要描述出一个功能AI 就能交付一个完整、正确、可维护的模块。实际上AI 更像一个“非常熟悉语法但缺乏项目上下文的新同事”。它可以帮你写出 80% 的模板代码但剩下的 20%包括边界处理、异常分支、性能约束、依赖版本、安全校验往往需要人来决定。专业定义上这类工具属于“代码生成模型”而非“程序验证工具”。它在生成结束后无法自动证明代码在所有输入条件下都正确。这也是为什么“编译通过”和“功能正确”完全是两件事。如果缺少验证环节AI 生成的代码就是你项目里的隐性负债。1.2 常见翻车现场在实际项目中盲目使用 AI 编程工具通常会遇到下面几类问题。第一类是幻觉 API。AI 生成了某个函数或类但对应的库版本里根本不存在这个方法。编译阶段报错时很多人会把报错再贴回 AI让它“猜一个修复”。如果项目里有多个同名 APIAI 很可能继续给出错误版本。第二类是依赖混乱。AI 在生成代码时经常顺手告诉你“需要安装某个包”但它不会检查这个包是否维护、是否存在安全漏洞、是否和当前 SDK 版本兼容。于是项目里出现一些“ AI 建议安装”的依赖过几个月再看已经和主框架冲突了。第三类是边界失控。AI 非常擅长生成“主流程代码”却容易忽略输入为空、数量为负数、数据超长、并发冲突等情况。这类问题往往不是编译错误而是运行时错误或业务逻辑错误测试用例很难发现。第四类是安全风险。如果 AI 被要求生成 SQL 拼接、文件上传、鉴权逻辑它可能直接给出一个有注入风险的实现。代码能跑但线上环境随时可能被打穿。这些翻车现场背后有一个共同原因使用者跳过了工程流程中的关键环节直接把 AI 输出当成最终交付物。1.3 工程师级 AI 编程工作流是什么工程师级 AI 编程工作流可以简单理解为一套“把 AI 放进开发环节”的系统化流程。它的核心理念不是“让 AI 少干活”而是“让 AI 在约束条件下干活”。这套工作流通常包含六个环节需求澄清把模糊的业务需求转成明确的输入、输出、约束、异常分支。任务拆分把大功能拆成多个可独立验证的小任务。上下文准备给 AI 提供足够但不过量的项目背景、代码风格、依赖信息。生成与迭代让 AI 生成候选代码通过追问和反馈逐步修正。审查与验证人工检查代码逻辑并补充自动化测试。提交与维护保持小步提交记录变更方便后续排查。标题里那句“别瞎用 AI 写代码”本质就是在提醒开发者AI 输出的是“候选方案”不是“最终答案”。只有经过人工作为“验收者”去审查、测试、修正代码才具备交付质量。2. 环境准备与工具选型2.1 工具清单本文示例项目以 TypeScript 为主原因是 TypeScript 的类型系统可以帮助 AI 在生成阶段就暴露一部分接口错误也方便我们建立“编译即校验”的开发习惯。你可以根据实际团队栈换成 Python、Java、Go但本文的核心工作流思路是通用的。推荐工具如下操作系统macOS / Linux / Windows 均可本文命令以常见 shell 为例。IDECursor 或 VS Code GitHub Copilot二选一即可。运行时Node.js 18 或更高版本。版本需要根据你的项目实际情况调整本文以常见 LTS 环境为例重点演示配置思路。包管理器npm 或 pnpm。语言与测试TypeScript、Vitest。版本管理Git。版本的精确数字不应照搬因为不同项目的依赖约束不同。推荐在创建项目时使用npm init或框架脚手架工具生成再根据需求调整版本。2.2 创建示例项目结构先创建一个用于实战的项目目录后续所有代码都放到这个结构里ai-order-checkout/ ├── package.json ├── tsconfig.json ├── src/ │ └── order.ts └── tests/ └── order.test.ts在终端中执行mkdir ai-order-checkout cd ai-order-checkout npm init -y npm install -D typescript vitest npx tsc --init这里的typescript用于类型检查vitest用于单元测试。安装完成后编辑package.json加入以下脚本{ name: ai-order-checkout, version: 1.0.0, type: module, scripts: { test: vitest run, test:watch: vitest, typecheck: tsc --noEmit }, devDependencies: { typescript: ^5.4.0, vitest: ^1.6.0 } }tsconfig.json是 TypeScript 的编译配置。为了让 AI 生成的代码更规范建议开启严格模式{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, strict: true, noEmit: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src, tests] }在这个配置下noEmit表示只做类型检查而不输出 JavaScript 文件strict会强制处理null、undefined等边界情况。这一点对 AI 编程尤其重要AI 生成的代码如果没有处理好边界TypeScript 编译器会直接报错而不是等到运行时出问题。3. 工程师级 AI 编程工作流总览3.1 六个核心环节在把需求交给 AI 之前先要想清楚整个开发流程。下面这套流程适合大多数中小型功能模块需求澄清 - 任务拆分 - 上下文准备 - 生成与迭代 - 审查与验证 - 提交与维护每个环节的输入和输出如下环节输入输出主要动作需求澄清业务描述接口定义、约束列表、边界条件写清楚输入输出、异常分支、性能要求任务拆分需求文档若干小任务按“可独立测试”的方式切分上下文准备项目代码提示词把文件路径、依赖、风格规范写进提示词生成与迭代提示词候选代码让 AI 生成初版再通过追问修正审查与验证候选代码可运行代码 测试人工 code review补充单元测试提交与维护验证通过的代码Git 提交记录小步提交写清楚变更原因很多开发者只做了“生成与迭代”和“提交与维护”两步中间的上下文准备、审查与验证都被省略了。这样看起来效率高但长期维护成本反而更高。3.2 为什么这是“工程师级”同样一句“帮我写一个订单金额计算函数”初级用法是直接把这句话丢给 AI得到代码后复制粘贴。工程师级用法则会多出几个步骤先定义清楚接口。订单金额计算需要哪些字段是否包含折扣折扣规则是固定金额还是百分比浮点数如何处理这些信息不明确时AI 只能“猜”而猜出来的业务规则大概率与产品预期不一致。然后设定约束。项目是否允许引入新依赖是否必须使用不可变数据结构返回的金额是否保留两位小数这些约束写在提示词里AI 生成代码时会明显更收敛。最后是验证。在 AI 生成代码后工程师会要求它补充测试用例并且自己审查测试覆盖了哪些边界。只有测试通过代码才算是“候选方案”变成“可合入方案”。这一整套动作的实质是“质量内建”与其等代码上线后由用户发现问题不如在生成阶段就把验收标准写清楚把边界条件交给测试去验证。4. 完整实战用 AI 开发订单计算模块这一节我们用一个订单计算模块来演示完整流程。需求描述如下实现一个订单工具模块支持商品明细的价格计算与数量校验。折扣规则支持固定金额与百分比两种类型商品数量必须是正整数且单件商品不能超过 99 件金额计算需要考虑浮点误差。4.1 第一步把需求转成任务说明不要直接把需求原文发给 AI。我们先把它拆成两个可独立验证的小任务定义商品明细模型、折扣规则模型、校验结果模型。实现订单总价计算函数和数量校验函数。然后编写提示词。下面是一个比较稳定的模板请用 TypeScript 严格模式实现一个订单工具函数模块输出到 src/order.ts。 需求 1. 定义 LineItem 接口字段为 sku: string、name: string、unitPrice: number、quantity: number。 2. 实现 calculateOrderTotal(items: LineItem[], rules?: DiscountRule[]): number。 3. DiscountRule 接口字段为 type: fixed | percent、minQuantity: number、value: number。 4. 实现 validateQuantity(items: LineItem[]): OrderValidationResult校验数量必须为正整数且不超过 99。 5. 金额计算要规避浮点误差最终结果保留两位小数。 6. 不要引入额外依赖不要修改其他文件。 约束 - 使用 export 导出所有类型和函数。 - 对边界情况添加明确注释。 - 请同时生成 4 个单元测试用例文件放 tests/order.test.ts。这里的关键是明确“输入输出、约束、边界、验收方式”。AI 收到的信息越具体生成的代码越接近你的预期。4.2 第二步让 AI 生成核心模块把上面的提示词发给 AI 后你可能会得到初版代码。接下来需要做一次人工审查和整理。下面是一个经过审查修正后的可运行版本。先在src/order.ts中定义核心类型与函数// 文件路径src/order.ts export interface LineItem { sku: string; name: string; unitPrice: number; quantity: number; } export interface DiscountRule { // 折扣类型fixed 为固定金额percent 为百分比 type: fixed | percent; // 触发折扣的最低商品总件数 minQuantity: number; // fixed 类型表示优惠金额percent 类型表示折扣比例例如 10 表示 10% value: number; } export interface OrderValidationResult { valid: boolean; errors: string[]; } // 单件商品的最大购买数量 const MAX_QUANTITY_PER_SKU 99; export function calculateOrderTotal( items: LineItem[], rules: DiscountRule[] [] ): number { if (items.length 0) { return 0; } // 1. 计算商品原价 const subtotal items.reduce((sum, item) { return sum item.unitPrice * item.quantity; }, 0); // 2. 应用折扣规则 // 简化处理满足触发条件时取最大优惠不叠加多个规则 let discount 0; const totalQuantity items.reduce((sum, item) sum item.quantity, 0); for (const rule of rules) { if (totalQuantity rule.minQuantity) { continue; } if (rule.type fixed) { discount Math.max(discount, rule.value); } else { discount Math.max(discount, subtotal * (rule.value / 100)); } } // 3. 保留两位小数避免浮点误差 const finalTotal Math.round((subtotal - discount) * 100) / 100; return finalTotal; } export function validateQuantity(items: LineItem[]): OrderValidationResult { const errors: string[] []; for (const item of items) { if (!Number.isInteger(item.quantity) || item.quantity 0) { errors.push(商品 ${item.sku} 的数量必须为正整数); } if (item.quantity MAX_QUANTITY_PER_SKU) { errors.push( 商品 ${item.sku} 的数量超过单次限购 ${MAX_QUANTITY_PER_SKU} ); } if (!Number.isFinite(item.unitPrice) || item.unitPrice 0) { errors.push(商品 ${item.sku} 的价格异常); } } return { valid: errors.length 0, errors, }; }这段代码解决了几类常见问题空订单不会出现金额为NaN或undefined的情况。数量必须是正整数且不能超过限制。价格必须是有限数值且非负。折扣计算只取最大优惠规则更清晰也更容易测试。4.3 第三步编写并运行单元测试AI 生成的测试往往只覆盖“正常路径”我们需要主动补充边界测试。下面这份测试文件覆盖了空订单、普通计算、百分比折扣、数量超限等场景。// 文件路径tests/order.test.ts import { describe, it, expect } from vitest; import { calculateOrderTotal, validateQuantity } from ../src/order; describe(calculateOrderTotal, () { it(空订单金额为 0, () { expect(calculateOrderTotal([])).toBe(0); }); it(无折扣时按单价乘以数量计算, () { const items [ { sku: A, name: 商品A, unitPrice: 10, quantity: 2 }, { sku: B, name: 商品B, unitPrice: 5.5, quantity: 1 }, ]; expect(calculateOrderTotal(items)).toBe(25.5); }); it(满足件数触发百分比折扣, () { const items [ { sku: A, name: 商品A, unitPrice: 100, quantity: 5 }, ]; const rules [ { type: percent as const, minQuantity: 5, value: 10 }, ]; expect(calculateOrderTotal(items, rules)).toBe(450); }); it(固定金额折扣不会把金额减成负数, () { const items [ { sku: A, name: 商品A, unitPrice: 10, quantity: 1 }, ]; const rules [ { type: fixed as const, minQuantity: 1, value: 999 }, ]; // 简化实现中折扣后结果为负数时不进行截断这里用例用于暴露该问题 expect(calculateOrderTotal(items, rules)).toBe(-989); }); }); describe(validateQuantity, () { it(合法的商品数量返回有效, () { const items [ { sku: A, name: 商品A, unitPrice: 10, quantity: 5 }, ]; const result validateQuantity(items); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it(数量为 0 时返回错误, () { const items [ { sku: A, name: 商品A, unitPrice: 10, quantity: 0 }, ]; const result validateQuantity(items); expect(result.valid).toBe(false); expect(result.errors).toContain(商品 A 的数量必须为正整数); }); it(数量超过限购数时返回错误, () { const items [ { sku: A, name: 商品A, unitPrice: 10, quantity: 100 }, ]; const result validateQuantity(items); expect(result.valid).toBe(false); expect(result.errors).toContain(商品 A 的数量超过单次限购 99); }); });运行测试npm run typecheck npm test预期输出包括一组通过的测试用例。如果你的测试结果出现失败不要直接改测试去“骗过”断言而是要回头检查业务逻辑是否符合预期。4.4 第四步审查与修正在刚才的测试用例里我刻意留了一个“固定金额折扣把金额减成负数”的问题。这是 AI 生成代码时很典型的情况主流程正确但边界条件不完善。通过测试用例发现问题后我们需要修正calculateOrderTotal让最终金额不为负数。修改后的实现export function calculateOrderTotal( items: LineItem[], rules: DiscountRule[] [] ): number { if (items.length 0) { return 0; } const subtotal items.reduce((sum, item) { return sum item.unitPrice * item.quantity; }, 0); let discount 0; const totalQuantity items.reduce((sum, item) sum item.quantity, 0); for (const rule of rules) { if (totalQuantity rule.minQuantity) { continue; } if (rule.type fixed) { discount Math.max(discount, rule.value); } else { discount Math.max(discount, subtotal * (rule.value / 100)); } } const finalTotal Math.max(0, subtotal - discount); return Math.round(finalTotal * 100) / 100; }同步修改测试用例it(固定金额折扣不会把金额减成负数, () { const items [ { sku: A, name: 商品A, unitPrice: 10, quantity: 1 }, ]; const rules [ { type: fixed as const, minQuantity: 1, value: 999 }, ]; expect(calculateOrderTotal(items, rules)).toBe(0); });这个小例子说明了一个关键道理AI 可以生成初版代码但“业务兜底逻辑”往往需要人在测试过程中补齐。没有测试的 AI 编程是在盲人摸象。5. AI 编程提示词与协作技巧5.1 写提示词的五个要素把需求讲清楚是 AI 编程中最重要的一步。一个有效的工程提示词通常包含五个要素目标你要实现什么功能放在哪个文件。输入输出函数的参数类型、返回类型。约束是否允许引入依赖、是否使用严格模式、代码风格要求。边界处理空值、负数、超长值、重复值等。验收方式是否需要生成测试用例测试覆盖哪些场景。对比下面的写法帮我写一个订单计算函数。这样的提示词非常开放AI 会自己选择参数格式、返回格式、折扣规则最后生成的代码大概率不符合你的预期。更合理的写法是请实现 calculateOrderTotal(items: LineItem[], rules?: DiscountRule[]): number。 items 中每个商品包含 sku、name、unitPrice、quantity。rules 支持 fixed 和 percent 两种折扣。 要求返回金额保留两位小数且不会出现负数。同时补充测试用例覆盖空订单、折扣触发、数量超限场景。第二种写法把验收标准直接变成了测试用例AI 的输出会更聚焦。5.2 让 AI 解释代码与补测试很多开发者只把 AI 当“生成器”用完就结束。更好的习惯是追问“解释这段代码的时间和空间复杂度。”“这段代码在哪些输入下会出错”“请补充三个边界测试用例。”“如果 quantity 是小数validateQuantity 会发生什么”这些追问的价值在于AI 的回答会暴露它生成代码时没有考虑到的场景而这些场景往往就是线上故障的根因。例如你可以让 AI 解释为什么Math.round((subtotal - discount) * 100) / 100能规避浮点误差。它可能会提到二进制浮点表示、精度丢失等概念。通过这种交互你对代码的理解会比直接复制粘贴深刻得多。5.3 上下文管理与文件级操作在大型项目中不要让 AI 直接面对整个仓库。建议按“文件级”给它上下文明确告诉它“只修改src/order.ts”。把相关接口定义粘贴进对话而不是让它自己猜测。如果项目里有代码规范文档可以把关键规范摘录进提示词。AI 改完代码后使用 Git 的 diff 功能逐行审查git diff这个命令会显示所有改动。你要检查的不只是语法还包括是否删掉了原有的核心逻辑、是否引入了与需求无关的修改。AI 在一次会话中改动越多回归风险越大所以尽量保持小步提交。6. 常见问题与排查思路AI 编程过程中会遇到很多看起来神秘的问题下面整理一份高频问题表。问题现象常见原因解决思路AI 生成了不存在的 API模型幻觉或训练数据版本过时先让编译器报错再查官方文档验证粘贴代码后出现依赖冲突项目中没有对应依赖或版本不兼容查看 package.json只让 AI 修改目标文件AI 一次修改大量代码后运行失败改动范围过大回归难定位拆分成小任务逐段生成配合 git diff 审查测试通过但业务场景报错测试用例覆盖不足边界未验证让 AI 补充边界测试使用属性测试验证随机输入AI 生成的 SQL 存在注入风险提示词里没有安全约束明确要求“禁止拼接字符串”改用参数化查询AI 反复给出同一个错误修复上下文里缺少报错根源信息把完整堆栈、数据类型、输入样例贴给 AI遇到报错时推荐按下面的顺序排查看编译器和类型检查器的报错优先修复类型错误。看运行时的完整堆栈而不是只复制最后一行。把项目相关文件路径贴给 AI让它缩小排查范围。如果 AI 修复了三次仍然无效停下来人工排查不要继续“盲猜循环”。修复后补充一个测试用例避免同样的问题再次出现。这条排查顺序的核心是“用系统化手段缩小问题范围”而不是依赖 AI 的运气。7. 最佳实践与工程建议7.1 提示词层面的建议写提示词时尽量把“不要做什么”也写进去。例如不要引入额外依赖。 不要修改其他文件。 不要使用 any 类型。 不要忽略边界输入。这类否定约束能显著减少 AI 的自由发挥空间。特别是“不要修改其他文件”这一条在多人协作的仓库里非常管用。另外建议把提示词沉淀到项目文档中。当团队里多个开发者使用 AI 编程时统一的提示词模板能保证输出风格一致也能减少代码 review 的负担。7.2 代码仓库层面的建议AI 生成的代码同样需要版本管理。不要直接在主干分支上让 AI 改代码推荐流程是新建功能分支。在分支上让 AI 生成候选代码。运行测试并通过代码审查。再合入主干。每次 AI 生成后建议用git diff检查变更内容。如果 AI 做了超出需求范围的改动比如顺手重构了另一个函数要立刻撤销这部分修改避免 review 范围失控。7.3 安全与合规这是 AI 编程中容易被忽略的部分。不要把数据库密码、云厂商密钥、用户隐私数据贴进 AI 对话。很多 AI 编程工具会把对话内容发送到云端进行处理一旦包含敏感信息就可能造成数据泄露。在涉及权限、支付、删除数据、安全校验等高风险场景中AI 代码必须在测试环境验证通过并由团队成员进行代码审查。所有变更都要有审计记录能做到“改了什么、为什么改、谁改的”都可追溯。7.4 团队协作AI 编程不是一个人的事。团队协作时可以约定一套共用提示词模板并通过 Code Review 环节统一代码规范。推荐在仓库中维护一份docs/ai-prompt-template.md记录以下内容项目的 TypeScript 版本与风格规范。常用的提示词片段比如“生成单元测试”“审查类型安全”“优化性能瓶颈”。禁止事项比如“不要引入新依赖”“不要修改无关文件”。这样每个开发者使用 AI 编程时都能基于同一套标准工作而不是各写各的提示词、各按各的喜好处理代码。8. 总结与下一步学习路线本文围绕工程师级 AI 编程开发工作流讲清楚了为什么“别瞎用 AI 写代码”。核心要点可以归纳为三点。第一AI 编程工具是生成器不是验证器。它适合生成候选代码但无法证明代码在边界条件下正确需要编译器、测试用例和人工审查来兜底。第二工程师级工作流的核心是“先定义验收标准再让 AI 生成”。需求澄清、任务拆分、上下文准备、测试验证这四个环节每一个都比“让 AI 写出完美代码”更重要。第三安全与维护意识不能丢。不要向 AI 提交密钥不要让它大范围修改代码不要跳过 Code Review。AI 能提高产出速度但代码质量和安全边界仍然由工程师负责。下一步建议从以下方向继续深入把示例项目中的订单模块扩展为包含库存扣减、优惠券叠加、支付金额一致性的真实服务。学习 TypeScript 的严格模式与类型体操提升与 AI 协作时的类型约束能力。引入自动化测试和 CI 流水线让每次 AI 提交都经过类型检查、单测、构建三步验证。建立一个属于自己的提示词模板库把常用业务模块的提示词积累下来逐步形成团队知识沉淀。如果你现在正准备把 AI 编程接入日常开发不要从大型系统重构开始先从一个小模块、一个小函数的提示词做起跑通“生成—测试—审查—提交”的闭环再逐步扩大 AI 的参与范围。这样既能感受到效率提升也不会把项目变成 AI 生成代码的实验场。