上个月我们组在代码评审时爆发了一场小争吵,导火索是一段AI生成的状态机实现。写代码的同事说他逻辑核过、测试也过了,但另一位负责维护订单模块的同事看完直接回了句:“这代码单独看没毛病,但它把我们整个模块延续两年的错误处理风格全改掉了。”单看每个commit都合理,串起来就是一次没有评审记录的小型架构漂移。这事之后我做了一个决定:给项目新增一套专门给AI编程流程制定的代码规范。
我说的“AI代码规范”,不是写给人类开发者的开发手册,也不是那种挂在wiki里吃灰的制度文档,而是给Claude Code、Cursor、Copilot、Cline这类AI编程助手用于对齐项目上下文的行为准则。它解决的核心问题只有一个:让AI产出的代码稳定符合团队既有约定,而不是每次生成都凭概率去猜。如果你正在高频使用AI提交代码,或者已经被AI那种“平均风格”坑过几次,这篇内容应该能帮你少走不少弯路。
1. 为什么我决定给AI单独制定一套代码规范
1.1 AI眼里其实不存在“项目约定”
人类开发者进入一个新项目,会做几件很自然的事:翻历史代码、看提交记录、问同事某个包为什么这么写。这些动作背后是在获取“项目约定”。约定不等于规范文档,它是散落在代码、注释、review记录里的隐性知识。
AI编程助手不一样。它每次会话开始时,对项目基本一无所知,看到的只是你塞给它的那点上下文。模型生成代码时,会强烈倾向于输出它在训练数据里见过最多、最“平均”的写法,而不是你们团队特有的写法。这就是为什么你让AI写一个错误处理,它大概率会按最通用的模式来,哪怕你们项目里所有代码都用了另一种封装。
这不是模型笨,而是它在没有额外约束时,默认选择概率最高的输出。要让它遵循项目约定,唯一的办法是把约定显式写出来,喂给它。否则,AI每生成一段代码,都是在和你们的历史约定做一次隐形的概率对抗。
1.2 传统的人类规范文档,AI其实读不进去
我们原本不是没有规范。团队wiki里有一份五千字的《开发规范》,从命名、分层到commit message格式都写了。我一开始也试着把这份文档直接丢给AI当上下文,效果很差。
原因是AI读规范和人类读规范完全两码事。人类看文档会跳读,能根据当前任务快速过滤无关内容;AI虽然能把文档塞进上下文,但它对文档里所有文字的注意力是相对平均的。导致一个典型问题:规范里写“一般情况下,请使用构造器注入”,AI会把它理解成“偶尔可以不遵守”。规范的初衷是留点弹性,结果弹没了。
更麻烦的是,传统规范里大量解释性文字、背景说明、“为什么这么写”的内容,本身会稀释真正的约束性指令。AI抓不住“哪些是硬性规定、哪些是背景介绍”。所以给AI用的规范,必须重构话语体系,把硬性规则和解释说明清晰分隔开,甚至只保留规则本身。
1.3 给AI定规范,本质是上下文工程
想清楚这一点很重要。给AI制定代码规范,本质不是“定制度”,而是做上下文工程。
代码规范是团队知识沉淀的产物,但传统沉淀形式是给人看的。AI参与的开发流程出现后,团队知识多了一个新的消费对象——模型。你要做的,是把“人读的知识”转换成人机共读的、结构化的行为约束。
具体来说,这套规范要达到三个目标。第一,可加载:AI在启动任务时能自动读到,不需要每次手工复制粘贴。第二,可执行:每条规则都是明确指令,没有“尽量”“可能”这类模糊措辞。第三,可校验:规则描述的行为能被静态检查、代码评审或测试所验证。后面这三个目标会贯穿整个方案的设计。
2. 给AI的代码规范长什么样:三层结构设计
如果只把规范写成一个超长文档,那等于没写。我给项目设计的规范分三层,每层解决不同粒度的问题,也匹配不同的AI工具加载机制。
2.1 第一层:项目宪法,全仓统一约束
第一层放在仓库根目录,命名为AGENTS.md,我习惯叫它“项目宪法”。它回答的是“在这个项目里,所有人都必须遵守什么”这类全局问题。内容不用多,一般不超过40条,但每一条都必须能映射到一个真实的项目事故或长期约定。
下面直接上一份精简版模板,你可以复制到自己的项目里改。
# AGENTS.md — 项目级AI协作规范(v1.4) ## 项目背景 - 后端:Go 1.22 + gin + MySQL/Redis,DDD分层(interface/application/domain/infrastructure) - 前端:Vue3 + TypeScript + Vite - 部署:K8s + Helm,配置统一走 config center ## 硬性禁止项 - 禁止修改 domain/ 目录下实体的公共方法签名,如需变更先写设计提案 - 禁止在 infrastructure 层之外的任何地方直接引用 gorm.DB 类型 - 禁止通过 init() 初始化业务依赖,依赖注入必须走 constructor - 禁止新增全局变量 ## 强制要求 - 业务错误必须用 errors.WithStack 包装,并统一返回 *appError - 日志必须结构化,采用 logger.Info(ctx, "user.created", "user-id", userID) 格式 - 所有对外HTTP接口的 handler 层必须显式传递 trace-id 参数 - 新写代码优先放到已有文件里,除非当前文件超过800行,否则不要新建文件注意到没有,这份规范里没有一句背景解释。每一条都是“禁止X”或“必须Y”,这是和人类文档最核心的区别。如果你想说清楚背景,放到规范文件末尾的“背景补充”区块里,并用分隔线隔开。
2.2 第二层:技术栈与模块约束
第二层是模块级规则,放在关键子目录下,比如 /src/user/AGENTS.md、/cmd/worker/AGENTS.md。它解决的是局部问题:这个模块有什么特殊约定、依赖边界是什么、哪些外部包的用法在本模块内是禁用的。
模块级规则的优势是“就近加载”。AI在处理某个目录下的文件时,工具会自动加载该目录的AGENTS.md,这样规范跟任务的距离更近,被遵守的概率更高。
举个例子,我们用户模块的局部规则文件长这样:
# /internal/user/AGENTS.md ## 模块边界 - 本模块禁止依赖 order 域的任何内部 service,需要订单数据时通过事件发布/订阅 - Repository 只允许出现在 infrastructure 层,domain 层禁止出现 SQL - 所有 DB 查询方法必须接收 context.Context 参数,禁止使用 context.Background() ## 编码约束 - 用户实体的状态变更必须通过 domain event 记录,禁止直接改 status 字段 - 查询列表接口默认必须支持 page/pageSize 参数,返回体使用统一 PageResult 结构模块级规范的建议篇幅是5到15条。太少说明没总结出局部约束,太多说明项目模块划分可能已经失控了。编这个文件时,最好拉到对应模块过去三个月的review记录,看看哪些问题反复出现。
2.3 第三层:任务级动态约束
第三层不放在文件里,它出现在每个AI任务启动时的prompt中。作用是针对这一次任务给出特殊约束,覆盖项目规范和模块规范之外的临时要求。
我整理了一套任务级约束的模板,推荐直接用:
本次任务约束: - 只修改【模块/文件】,其他文件除非必要否则不要动 - 不允许对非相关代码做重构 - 保持对外接口兼容,不得修改已有方法的签名 - 提交信息必须遵循 conventional commits 格式 - 本次任务生成的代码必须自带单元测试任务级动态约束的优先级最高。我后面会细说优先级设计,这里先记住一句话:离任务越近的规则,AI越容易遵守。文件里写了十条规则,可能不如你在prompt末尾加一句“本次只准改这三个文件”管用。
3. 怎么让AI真正“看见”并遵守规范
规范写出来只是第一步,怎么让AI每次都能读到并且服从,才是真正花时间的地方。
3.1 文件位置、命名与目录覆盖规则
目前主流的AI编程工具对规范文件的支持越来越统一。Claude Code会自动加载CLAUDE.md,Cursor从某几个版本开始支持 .cursor/rules,Cline、Continue等开源工具对AGENTS.md的支持也比较成熟。
我的建议是:以AGENTS.md为唯一事实源,根目录放全局规范,关键子目录放局部规范。然后让CLAUDE.md只写一行引用,避免两份文件内容漂移。具体做法:
# CLAUDE.md 请先阅读根目录的 AGENTS.md,并严格按照其中的规则执行。 子目录下的 AGENTS.md 优先级高于根目录文件,当两者冲突时,以子目录文件为准。文件加载遵循就近优先的原则。AI在处理 /internal/user/xxx.go 时,会同时读到根目录和 /internal/user/ 下的AGENTS.md,遇到冲突时以离任务更近的模块级文件为准。
这里有一个很容易踩的坑:多个规则文件内容重复且互相矛盾。比如根目录写“禁止直接使用 gorm.DB”,但 /internal/user/AGENTS.md 里又写了“查询必须通过 gorm 链式调用实现”,AI就会陷入随机选择。解决方法是每个目录的规范只补充该目录独有的约束,不要重复粘贴全局规则。
3.2 把规范写进任务启动流程
规范文件被动加载还不够。AI工具加载AGENTS.md是有条件的——不是每个工具、每个模式都会自动读取。为了确保万无一失,我把规范引用做进了任务启动模板。
在团队内部的需求模板里,我加了一个“AI编码注意事项”字段,每次下发给AI的任务描述,默认会在开头带上这句话:
你是一名资深Go工程师,请先阅读根目录 /AGENTS.md 和本次任务涉及模块下的 AGENTS.md,然后严格按照规则完成任务。规则中未明确允许的写法,默认视为不允许。最后一句“规则中未明确允许的写法,默认视为不允许”很关键。它把AI的默认行为从“按训练分布自由发挥”切换成“按规则约束谨慎输出”,语气不一样,结果差别很大。实测下来,加了这句话之后,AI生成代码的风格稳定性提升明显。
如果是个人项目,或者工具不支持自动读AGENTS.md,可以把规范文件路径直接贴在prompt里让AI自己读,效果比不贴好很多。
3.3 CI增加一道“AI代码体检”闸门
规范写得再好,AI总有走神的时候。所以我在CI流程里加了一道针对AI生成代码的检查闸门,作为兜底。
做法是在git提交信息里识别AI参与标记。Co-Authored-By这个trailer现在很多AI工具会默认带上,我们就在pre-commit或CI脚本里检测:如果提交包含该标记,就自动启用更严格的规则集。
更严格的规则集包括三块:一是ESLint或golangci-lint里项目和AI自定义的额外规则;二是自定义AST检查脚本,扫描某些约定是否被破坏,比如是否出现了规则文件中禁止的调用模式;三是自动跑一次相关模块的单元测试,AI改动的文件必须测试覆盖率达到预设阈值。
这个闸门一开始会误伤,主要是有时候AI参与但改动很小,没必要跑全套。后来我们加了一个优化:只对AI改动行数超过20行的提交启用完整检查。少了就当普通改动处理,效率明显提升了。
4. 实施过程中的关键细节与避坑记录
4.1 规则要写成“命令”,不是“建议”
这是给AI写规范和给人写规范最大的区别。人看到“建议使用构造器注入”,知道这是强约束;AI看到这句话,会把它当成一个概率非常高的建议,但内心并不排斥偶尔违背。为了让指令风险最小化,措辞上要非常刻意。
我踩过几次坑之后,总结了三句话:
- 不要用“尽量”“推荐”“通常”这类弹性词,直接写“必须”“禁止”
- “不要做X”不如“请使用Y替代X”有效,因为AI不知道X的替代方案时,可能绕回去用X
- 每一条规则尽量跟一个正面小例子或反例,AI对例子的理解远强于抽象描述
举一个实际改过的例子。最初规范写的是:“不要使用fmt.Errorf,推荐使用errors.Wrap”。AI生成代码时仍然混着用。改成下面这样的格式后,立即稳定了:
错误处理:业务错误必须使用 errors.WithStack(err),禁止使用fmt.Errorf,示例: if err != nil { return nil, errors.WithStack(err) }没解释原因,没有“为了统一可观测性”这类背景,只给命令和示例。AI就能精确复制。
4.2 防止规则库“年久失修”
规范文件最大的敌人不是AI,是过期。项目演进之后,当初的约束可能已经不再适用,但规则文件还在,AI会老老实实地遵守一条已经没有意义的规则,甚至因此拒绝正确的实现。
我为每份规范文件加了version和last_reviewed字段,每季度安排一次规则评审会。同时明确了一条流程:新规则从提出到正式生效,必须先经过两周的“试行期”。试行期内规则写在任务级动态约束里,如果两周内有效避免了原本的问题,再正式写入AGENTS.md。
有一个更细的经验是:某条规则如果被AI连续违反三次以上,先别急着加大惩罚力度——比如在文件里加更多感叹号、大写、重复强调,这些都没用。大概率是规则本身描述有歧义,和现有代码范式冲突,或者AI在实际场景里找不到符合该规则的写法。这时候应该回看AI生成的代码,理解它为什么不遵守,然后改规则的描述,而不是改规则的语气。
4.3 规则冲突时的裁决原则
规则一多,冲突不可避免。我为团队定了一个明确的优先级排序:
任务级prompt约束 > 模块级AGENTS.md > 根目录AGENTS.md > 模型默认行为为什么会这样设计?因为离任务越近的指令越具体,越具体的东西应当覆盖越通用的约束。如果用户在prompt里说“本次暂不考虑错误处理,先打通主流程”,那模块规范里的“必须errors.WithStack”就让位。
同时我建了一个rules-issues.md,专门记录出现的规则冲突和AI违反规则的反例。这个文件的价值是让规则评审会不再靠空想,而是有真实案例可以对照。每一条冲突记录都包含:冲突双方、出现的场景、当时的处理方式。等积累了一定样本,再批量调整规范。
5. 常见问题与排查技巧实录
5.1 规则都在,可AI就是不遵守,怎么办
我遇到“AI不守规矩”的时候,会按以下顺序排查。
第一步,确认AI真的读到了规则。直接在对话里问它:“请列出你当前的代码规则,逐条说明。”如果它总结不出来,说明规则根本没被加载进上下文,这时候要解决加载机制。第二步,检查规则之间有没有互相矛盾。拿一个典型代码示例跑一遍,看不同规则会不会给出不同做法。第三步,如果规则没问题,把“已经不遵守或经常不遵守”的行为放到“硬性禁止项”里,并给出一个反例。第四步,换更强的模型或调整模型参数。推理能力弱的模型在长上下文里本来就容易“失忆”,有时候不是规则的锅。
还有一个容易忽略的点:AI在超长对话里会逐渐偏移初始指令。如果对话轮次很多,后边几轮的输出往往不如开头遵守规则。这时候宁可新开一个会话,把关键上下文重新粘进去,也不要硬在一个超长对话里继续生成。
5.2 不同模型对规范的遵循能力差异很大
实测下来,不同模型对显式规则的遵循能力差距相当明显。Claude系列的长上下文遵循能力比较稳,开启规则文件后能持续稳定输出;GPT系列在明确指令下表现也不错,但对话变长后偏离的概率更高;一些开源或者推理能力稍弱的模型,在上下文较长时,很容易把规则“忘”在中间部分,只记住开头和结尾的指令。
这个差异带来的调整是:如果你主力模型是弱模型,规范文件结构要调整为“最重要的规则放在文件开头和末尾”,并且把规则数量压缩。一开始我们给所有模型用同一套规范,后来针对弱模型单独出了一份精简版,把40条压缩到12条,只保留硬性禁止项和最高频必须项,遵守率立刻上来了。
5.3 规范会不会拖慢AI生成代码的效率
短期看会有一点点适应成本。最初一周,AI生成代码后需要额外检查规则执行情况,单次任务耗时可能会增加一些。但整体来看,返工大幅减少,合入主干后的review争吵也少了很多,实际是赚的。
关键点是规范不能贪多。如果你一口气堆60条规则,AI的注意力会被分散,反而容易每条都不认真遵守。我的建议是从10条核心规则开始跑一个月,把最高频的返工问题解决了,再逐步增加。规范数量控制在40条以内比较合理,超过就要考虑拆分到模块级文件,而不是堆在根目录里。
最后再说一个我的个人体会。给AI定规范这件事,最难的是克制——克制自己想把所有经验都写进去的冲动。规则文件不是越厚越好,每条规则都必须对应一个真实问题,宁缺毋滥。我们跑了大概一个季度之后,AI提交的代码被review打回的频率明显下降。更意外的是,这些规则后来也成了新人入职的上手材料,他们看完AGENTS.md,比看wiki文档更快理解了项目的关键约束。如果你也在被AI代码风格不稳定折磨,建议从一页纸的规则开始,先定十条真正有用的,不要一上来就写一部法典。