
1. 为什么我给项目加了一份只给AI看的代码规范上个月我们前端组在做过一轮代码review的时候遇到一个以前从没见过的现象同一个需求拆出去三个成员分别让AI各写一版提上来的代码接口一样、功能也能跑但数据流处理方式、错误处理习惯、组件拆分思路完全是三个世界观。一个用了React Context管理所有状态一个坚持只在页面层用props传递还有一个直接引入了外部状态库理由是“AI说这个更适合中大型项目”。这不是个例。我回头看最近两个迭代的提交记录凡是标注了“AI辅助生成”的pull request风格一致性明显比纯手工代码差很多。人工写代码团队里的老人会通过代码review氛围、命名习惯、模块划分渐渐趋同AI写代码它每次都是从自己的统计分布里“猜”一个最可能的答案。同一个团队、同一个项目AI这次猜得像Angular风格下次猜得像函数式风格完全没有记忆更不会因为上个月我们用过某个模式就自动沿用。项目里原来那套给人写的代码规范放在角落的wiki里AI根本不知道它的存在。所以我决定在项目里新增一份“给AI制定的代码规范”。它不是给人看的或者说主要是给AI Agent、AI编程助手、代码补全模型在执行任务时读取的约束文件。这份规范的内容和传统开发规范有很大重叠但表达方式完全不一样不是一篇娓娓道来的文章而是一组短句、必选/必不选清单、项目专属约定和反面案例。把它写好后放进项目根目录的规则文件里让AI在生成代码之前先读一遍再开始动手。先说结论这份文件对AI生成的代码质量提升非常明显。之前AI提交代码的返工率大概在四到五次能合入一次现在基本一到两次就能过而真正让我坚持做下去的不是返工率这个数字而是团队终于不用再对着AI代码猜“它到底为什么要这么写”了。我知道很多团队对AI生成代码的态度是“让AI写人来做review”。这句话听起来没问题实际执行起来你会发现如果你没给AI框定边界review的人等于在给一个不了解项目背景的实习生擦屁股。而制定AI代码规范就是提前把这个“背景”塞进AI的上下文里。2. 落地前先盘点哪些规范值得写给AI哪些不值得给AI制定规范之前我先把项目里已有的规范、约定、历史禁忌全部梳理了一遍。这一步很容易被跳过——很多人上来就让AI生成一个rules文件结果写了一大堆泛泛而谈的原则AI看完还是不知道在你项目里该怎么做。2.1 从“人读规范”到“AI读规范”的翻译过程传统的开发规范是给人看的默认读者拥有大量项目上下文知道哪些模块是核心域知道某个目录为什么必须保持干净知道线上曾经因为什么事故定了一条约束。AI没有这些上下文它看到的是一堆零散文件和一行行prompt。所以翻译过程中最重要的不是复述原则而是把每个原则变成AI可执行的判定条件。举个例子我们原规范里有一条“组件设计要考虑复用性”。这句话人看了能理解AI看了只会写出过度抽象的组件。翻译成AI规则时我改成了“在没有第二个真正调用方之前禁止抽象公共组件当出现第二个调用方时提取公共部分到src/components/shared并添加JSDoc说明复用边界。”这样一来“复用性”从一个模糊的价值观变成了一个可检查的行为约束它规定了触发抽象的条件也规定了抽象后的落点。另外不是所有人类规范都适合写给AI。我们盘点后分成了三类一类是AI必须遵守的硬性约定比如目录职责、导入规则、状态管理边界一类是AI可以参考的软性模式比如组件怎么写、hooks怎么命名还有一类是根本不需要写给AI的比如团队内部代码review流程、发布节奏等AI不参与这些事情写了只会占用宝贵的上下文空间。2.2 盘点时我列的问题清单做盘点时我建议你带着下面这组问题过一遍项目项目有哪些目录每个目录的职责边界是什么AI能否根据目录名判断代码应该放哪里项目里有没有禁止使用的API或过时模式比如老的class组件、被废弃的生命周期方法、禁止直接修改state等。命名规范是否统一变量名是camelCase还是snake_case组件名是否要求以特定前缀开头错误处理策略是什么AI默认生成的是try/catch后console.error这在你的项目里能接受吗有没有推荐的第三方库清单AI经常自己发明依赖你需要告诉它什么情况可以加依赖什么情况必须问人。测试怎么组织单元测试、组件测试各自放在哪里mock的约定是什么样式方案是什么CSS Modules、Tailwind还是styled-componentsAI经常会同时引入好几种。把这些问题一个个回答完毕你得到的不是一篇规范文章而是一份“决策对照表”。这才是AI规范文件真正需要的素材。2.3 三类规则的价值取舍我建议不要追求大而全而是做减法。第一版我写了60多条规则结果AI在长上下文里反而抓不住重点该违反的还是违反。后来我砍到31条把规则分成三个层级项目级底线、模块级约定、风格级偏好。层级内容建议写给AI吗原因项目级底线目录职责、禁止依赖、状态管理边界必须违反后直接影响架构和可维护性模块级约定错误处理、接口封装、hooks规则应该影响代码质量和团队协作效率风格级偏好缩进、引号、是否加分号少写或交给格式化工具Prettier等工具已经能约束占用token不划算这个取舍很重要。AI上下文窗口再大也是有限的规范文件每多一条AI对核心规则的注意力就会少一点。所以我把能交给工具处理的规则全部交给工具只把需要AI“理解项目语义”的规则写进规范文件。3. 我给项目定义的三层AI代码规范结构附可直接抄的规则文件很多AI编程工具都支持读取项目根目录下的规则文件来影响生成行为不同工具有不同叫法有的叫Project Rules有的叫说明文档还有的通过特定文件扫描项目规范。核心思路是一致的在Agent开始写代码之前把项目约束作为上下文注入。我自己的项目用的是三层结构分别解决不同维度的问题。3.1 第一层项目根目录的全局规则文件这一层放的是所有AI任务都必须遵守的公共约束。以我们的一个TypeScript前端项目为例规则文件初始结构是这样的# 项目AI代码规范 ## 项目底座 - 本项目使用 React 18 TypeScript 5 Vite严格模式开启。 - 状态管理使用 zustand禁止引入 redux、 mobx 等其他状态库。 - CSS 使用 Tailwind CSS禁止引入 CSS-in-JS 方案。 - 所有新依赖的引入必须先向维护者确认AI 不得自行安装 package。 ## 目录职责 - src/app 只放路由和布局级组件。 - src/features 按业务模块组织每个模块包含 components / hooks / api / store。 - src/shared 放可复用基础组件和工具函数禁止在 shared 中写业务逻辑。 - 超过两层父子传递的 props优先考虑提升到 store不要在中间层透传。 ## 编码约定 - 组件文件使用 .tsx非组件模块使用 .ts。 - 函数组件声明使用 function ComponentName() {}禁止箭头函数导出组件。 - 私有函数使用 _ 前缀并在函数顶部写明用途。 - catch 代码块里必须有错误上报或用户提示禁止 catch 后直接留空。 - 禁止使用 any不确定的类型先查接口文档再穷举为联合类型。 - 所有异步接口调用放在 features 模块的 api 层禁止在组件里直接写 fetch。 ## 生成代码的硬性要求 - 新生成的代码必须兼容项目已有工具链不新增格式化配置。 - 复制现有代码风格不创造新的写法。 - 公共组件必须附带示例用法。 - 如果存在多个实现思路优先选择改动最小、调用方影响最少的方案。这段内容很直白基本没有修饰词。每条规则都是一句话AI更喜欢这种句式因为解析起来没有歧义。规则里特意写了一条“不创造新的写法”这句话对AI很有效因为AI默认倾向是“给我一个新点子”而项目维护恰恰最怕新点子。3.2 第二层按模块拆分的局部规则全局规则不能覆盖所有场景。我在src/features的每个业务模块目录下放了一个简短的局部规则文件内容更贴合该模块的特殊情况。例如支付模块的规则文件里有一条“所有金额计算必须使用decimal工具库禁止出现浮点数直接运算”报表模块的规则则要求“导出文件的字段名必须与后端接口返回字段保持一致禁止前端自行改名”。局部规则解决了全局规则太粗的问题。AI在读取代码时会优先感知到离它最近的说明文件所以局部规则的指令优先级其实比全局规则还高。我们就是在支付模块发现AI连续两次写出0.1 0.2直接比较金额后才意识到这类业务级规则必须下沉到模块里。3.3 第三层AI任务提示词里的临时约束前两层是挂在项目里的静态规则第三层是我在每次给AI派活时附加的临时约束。比如一个任务要求“新增用户列表页”我会在描述后面附带一段- 参考src/features/user/list目录下已有页面的实现方式保持结构一致。 - 不修改现有接口返回结构如果接口字段与页面展示不一致在api层做映射。 - 分页参数名与本项目其他列表页保持一致。 - 完成后列出新增文件清单和需要人工review的风险点。这些临时约束不会写进项目规则文件因为只有这次任务需要关注。它相当于每次开工前的“简报”让AI不会一上来就自由发挥。实际跑了几周后我发现一个规律AI出问题最多的往往不是复杂逻辑而是它不知道“这件事在我们项目里已经有过固定做法”。临时提示词就是补上这个信息差。4. 从“口头约定”到“机器可检查”把AI代码挡在可维护性红线之外规则文件写得再漂亮如果只依赖AI自觉效果还是会打折扣。AI有个特点上下文一长它会把规范忘掉任务一复杂它会优先保证“能跑”而不是“符合规范”。所以我在制定规范的同时把一部分规则变成了机器可检查的CI检查项。4.1 先把硬规则交给工具我们项目原本就有ESLint和Prettier但原来那些规则只检查代码语法和格式查不出架构层面的问题。新增AI代码规范之后我把能在工具层面落地的硬规则全部加进了ESLint。比如“禁止在组件里直接调用 fetch”我加了一条{ rules: { no-restricted-syntax: [ error, { selector: CallExpression[callee.namefetch], message: 禁止在组件中直接调用 fetch请封装到 api 层 } ] } }再比如“禁止 catch 后吞掉错误”我用ESLint的no-empty配合catch判定来兜底至少保证catch块里不是空的。针对“禁止使用 any”项目开了typescript-eslint/no-explicit-any为error级别。这些检查一旦进入CIAI生成的代码合入前就会被打回不需要人肉review每一行。4.2 review清单给人工检查也配一份“AI代码专项检查单”机器能拦住的规则只是一部分更多规则需要人review时判断。我整理了一份“AI生成代码专项review清单”挂在项目PR模板里让每次review的人逐项确认新代码是否理解了现有模块边界有没有把业务逻辑塞进shared组件命名是否与项目既有风格一致有没有发明同义词比如别人用removeUser它却新写一个deleteUserRecord有没有出现“AI自嗨型”抽象比如只为一次调用创建了泛型工具类。依赖列表有没有变化新增依赖是否经过人工确认测试是真正验证了逻辑还是为了让代码覆盖率变好看而写的错误路径是否被处理了用户能否看到可理解的提示。这份清单不要求全程勾选而是要求review者在认为有问题时引用清单条款。慢慢地AI生成的代码质量曲线变得可观测每周review时我们能看到主要问题集中在哪个模块、哪类规则这为后面迭代规范文件提供了依据。4.3 不要指望AI“记住”规则要用流程兜底有一个很反直觉的体验刚把规范文件放进去的头两天AI表现非常好到了第三天同一个Agent却开始违反规则。原因是Agent在解决问题时会优先把任务上下文塞进窗口规则文件里的内容被挤出了有效注意力范围。所以我不再把规则文件当成“AI会永远记得的东西”而是设计成“每次任务开始时必须重新读取”的环节。我们在派发任务时统一带上了“先读规则文件再开始编码”的指令同时CI作为第二道防线。这样即使AI把规则忘了提交代码时也会被发现。5. AI最容易触犯的六类规则以及我们的实际处置案例规范文件制定后我记录了两个迭代里AI高频违反的规则。这里列出最有代表性的六类也是我觉得任何想做类似规范的团队都应该提前设防的坑。5.1 过度设计为不存在的未来做抽象最典型的AI症状是“给一个简单页面生成三层抽象”。有一次我们让AI写一个简单的筛选功能它生成了一套FilterEngine加FilterStrategy加FilterConfig的复杂结构代码量翻了三倍。原因是训练数据里大量充斥着“可扩展架构”的代码AI认为这是一种高质量输出。我们的规则修正为禁止为单一调用场景创建抽象层抽象必须有第二个明确调用方。从那以后这类问题减少了八成。5.2 幻觉式依赖引用了不存在的库或APIAI生成代码时经常自信地引入一个项目里根本没有的依赖甚至这个依赖的名称都可能是幻觉。比如它推荐过common/utils这类内部包实际上这个项目里的公共工具都在src/shared/utils。更危险的是有些AI会“记得”某个库的API但记错了版本生成的调用方式在当前版本下根本不存在。我们的规则强制要求新增依赖前必须人工确认代码中出现的import路径必须能落到已有文件或明确待创建文件上。这两条规则直接跳过了大量“看起来对、跑不起来”的问题。5.3 错误处理被静默吞掉AI默认的错误处理模式是try/catch之后打一条console.error然后继续执行。在工具型代码里这可能无所谓但在我们的业务代码里静默失败意味着用户支付失败后还停留在成功页。规则写入“catch块内必须包含错误上报或用户可见提示禁止空catch。”review时如果发现catch块只有注释直接打回。这是一条完全靠人和规范文件共同作用的规则ESLint很难完全判断错误处理的语义所以更多靠review执行。5.4 命名与既有体系不一致同一个业务对象项目里已经叫taskAI却生成了一堆job、assignment、mission开头的变量。这种不一致不报错但对后期搜索和维护的影响是长期的。我们的规则里特别加了一条“新代码的命名必须与项目中出现过的业务词汇保持一致命名前先搜索代码库已有用法。”AI在短任务里大体能遵守但长对话后期容易飘review时仍需要重点盯着。5.5 测试变成实现的复读机AI写测试的常见问题是测试断言和实现逻辑长得一模一样。它把函数内部代码复制了一遍然后断言某个中间变量被调用了而不是验证最终输出是否符合预期。这种测试对代码质量没有保护作用反而因为覆盖率达标给了团队一种虚假的安全感。规则要求测试必须站在用户行为或输入输出角度编写mock必须独立于被测函数的内部实现。5.6 跨模块的“顺手改动”AI为了让自己的代码能跑通经常会顺手修改不相关模块的文件——改一个接口字段名动一个shared组件的样式把一个本来纯粹的utils函数加上业务依赖。这些“顺手改动”在diff里特别隐蔽因为AI会在PR描述里写“同时修复了xxx问题”。我们的规范加上了一条硬性约定“除非任务里明确提到禁止修改与任务无关的模块如果必须改列出理由单独说明。”同时review时用git diff过滤非任务文件发现问题直接打回。6. 规范文件不是写一次就完版本化迭代与效果评估最后说一下这份AI代码规范的迭代过程。第一版规则文件是在一个周末写出来的进项目后跑了两周问题不少有的规则太理想化AI做不到有的规则互相矛盾比如既要求“尽量复用公共组件”又要求“禁止过早抽象”AI不知道哪个优先。6.1 给规范文件也打上版本我后来把规范文件本身当成一个项目来维护。每个改动都记录在文件末尾的ChangeLog里写明哪条规则是为什么加的。例如## ChangeLog - v1.2 (2025-03-10)新增禁止在组件中直接调用 fetch 的规则原因是 AI 生成的支付模块代码绕过了 api 层。 - v1.3 (2025-03-17)修改抽象规则明确“必须有第二个调用方”才允许抽公共组件。 - v1.4 (2025-03-24)删除“尽量使用 useMemo”的规则因为 AI 过度使用导致性能反而下降。版本化最大的好处是当AI行为发生变化时能追溯是规范哪次改动引起的。有一次我们发现AI生成的代码突然都带上了奇怪的注释风格最后查明是规则文件中新增的一条例子不小心被AI当成了必须照抄的模板。快速回滚规则后问题就消失了。6.2 用数据判断规则该留还是该砍不要把规范文件当成一次性的静态清单。每个迭代结束后我会统计一次“AI代码违规分布”哪条规则被违反最多哪条规则从没被违反过没被违反过的规则如果它占用了大量上下文就考虑删掉或精简被频繁违反的规则除了加深字眼描述还要考虑能不能用一个ESLint检查项替代。从我们这几次迭代的数据看最容易被AI违反的始终是“命名一致性”和“错误处理”这两类最不容易被AI违反的反而是“禁止任何”这种绝对化禁令。这说明AI对“绝对禁止”类规则的理解力比对“尽量/优先”类规则强得多。所以后来我把软性描述都改成了带有条件和动作的硬性描述比如不再写“注意性能”而是写“列表超过100项时必须使用虚拟滚动”。6.3 给团队和AI一个磨合预期规范文件不是AI一读就能内化的。它更像一个“持续磨合”的过程你写规则AI用行为反馈你再修正规则。刚开始的一两周会有点沮丧因为AI还是会犯错。但如果每次AI犯错你都能回到规范文件里找到对应条款要么严格打回要么补充规则那么大约三到四个迭代后AI生成的代码就会明显向规范收敛。我个人在实际项目中体会最深的一点是给AI制定代码规范本质上不是约束AI而是把项目里那些只有老人知道的“潜规则”翻译成AI能听懂的语言。翻译得越清楚团队review的时间就越短人就能把精力放在真正需要判断力的地方。整个过程没有太多高深的技术就是把每个“为什么”都写下来然后让所有人——包括AI——照着做。