最近在体验 Codex 辅助编程时,我逐渐察觉到一种常见的困境:

AI 并非不会编写代码,它的真正问题在于——它实在太“擅长”写了。
你让它修改一个接口字段,它就会自动补上一套兼容逻辑:
const title = data.title || data.name || data.label || data.displayName || ''
你让它调整一个按钮文字,它却顺手重构了大半个组件。
你让它补充一个 loading 状态,它反而抽出了一个 hook、拆分出工具函数、添加了类型定义、优化了代码结构,末了还贴心地告诉你:“这样更具可维护性。”
这种场景,相信很多人都曾经历过。
好比请朋友来家里换个灯泡,结果他打量了一下天花板,说:“你这套照明系统设计存在隐患,我帮你重新规划电路吧。”
感谢好意,但我只希望灯能亮起来。
因此,为 Codex 制定一套开发规则——也就是像 codex-dev-norms 这样的技能文件——就成为一件很自然的事。这套规则的目的不是让 AI 变笨,而是让 AI 在动笔写代码之前先明白一条原则:
并不是所有能写的代码,都应该写。
一、背景:AI 写代码时常见的“热心过度”现象
在 AI 辅助开发中,有一个非常典型的问题:AI 往往会“补充过头”。
人类工程师写代码时,通常会带着项目上下文、团队约定、业务边界,以及一点点因线上事故产生的心理阴影。
AI 则不同。它擅长从已有模式中推断下一步,也爱把“不确定”的内容补成“看上去合理”的结果。这在很多场景下确实有用,但在业务代码里,它也可能制造出一系列我们非常熟悉的事故现场。
1. 过度兼容
比如接口文档明明只写了 title,AI 却担心后端可能会返回 name,历史数据也许会有 label,保险起见再兼容 displayName。
于是代码变成了:
const title = item.title || item.name || item.label || item.displayName
乍看很稳健,实际上隐患重重。
因为它把一个明确的接口契约,变成了一个字段猜谜游戏。今天兼容四个字段,明天又加一个 text,后天再补一个 caption,最后谁也不知道真实数据应该长什么样。代码逐渐像在相亲:谁来都能接一下。
2. 过度重构
你只是想让它修复一个 bug,它却觉得附近代码不够优雅。
“这个函数可以抽取一下。” “这个组件可以拆分一下。” “这个命名可以统一一下。” “这个结构可以更通用一点。”
如果是在学习项目中,这或许让人开心。但在真实业务项目里,这种“顺手优化”经常导致范围失控。一个小改动突然牵连多个文件,原本只需要验证一个按钮,现在却要确认半个页面是否回归。AI 很积极,而做 Code Review 的人只能沉默。
3. 过度抽象
AI 看到重复代码,很容易触发封装冲动。两个地方出现相同的状态判断?抽。两个组件样式相似?抽。两个类型字段差不多?合并。
但工程中的重复并不都是坏事。有些重复只是表象相似,业务含义却完全不同;有些代码当下重复,未来走向可能截然不同。强行抽象之后,项目有可能从“有一点重复”升级为“没人敢动”。
4. 忽略编码与中文环境
中文项目还有一个经典问题:编码。尤其在 Windows 环境下,如果读写文件时不确认 UTF-8、GBK、ANSI,轻则中文变乱码,重则打开文件那一刻,你会觉得项目正在用另一种文明与你沟通。
还有些 AI 会把中文写成一段 Unicode 转义字符。虽然程序可能正常运行,但人类读起来只想下班。
5. 缺少“改动边界感”
归根到底,很多问题并非 AI 不懂代码,而是它不知道边界。它不清楚哪些文件不能改动,哪些字段不能猜测,哪些重复理应保留,也不了解当前项目需要的是“修复”而非“翻新”。
因此,我们迫切需要一套规则,让 AI 在动手前先得到明确提醒:只做必须做的事,不做看似也不错的事。
二、有哪些可行的解决办法
要解决 AI 过度发挥的问题,常见办法有几种。
1. 依靠人工 Code Review
这是最传统也最可靠的方式。AI 写完代码后,由人类来审查。问题在于 AI 写得太快了。它一分钟就能生成大段代码,而人类需要逐行确认:这个兼容是否有必要、这个抽象是否有收益、这个字段是否基于猜测、这个文件是否不应改动。久而久之,Code Review 从“质量保障”变成了“AI 善后现场”。
2. 依靠 Prompt 反复提醒
比如每次对话中都明确告诉 AI:“只做最小改动。不要重构。不要猜测接口字段。不要修改无关文件。”
这当然有用,但问题是要每次都重复。只要有一次忘记提及,AI 就可能立刻恢复出厂设置,重新变成那个看到代码就想顺手优化的热心同事。
3. 依靠项目级规则文件
比如 AGENTS.md、CLAUDE.md 或者 Codex 的 skill 文件。这种方式的好处是将团队约定固化,不必每次都靠人工提醒,而是让 Codex 在工作时就能读取项目边界。哪些事可以干,哪些事不能干;什么时候需要查阅接口,什么时候必须保持最小改动;什么时候该加测试,什么时候不要为了覆盖率写无用测试。
这就相当于给 AI 配发了一份“入职手册”。只不过这份手册的重点不是“欢迎加入团队”,而是“欢迎加入团队,先别乱动”。
4. 依靠技能化拆分规则
更进一步,可以把规则拆分成多个 reference。例如 API 有 API 的规则,前端有前端的规则,测试有测试的规则,Git 工作区有 Git 工作区的规则。这样 Codex 无需每次都背完整本制度,而是根据任务读取相关部分。修接口就看 API 规则,改组件就看前端规则,做 review 就看 review 规则。
这种方式更接近真实的工程协作:不是把 AI 当作万能文本生成器,而是把它当作需要遵守项目规范的开发者。
三、这套 skill 具体解决了哪些问题
codex-dev-norms 这套 skill 的核心价值,在于把 AI 常见的失控点拆解成一个个明确的边界。它不是一份“代码洁癖清单”,更像是一份“防止 AI 过度热心说明书”。
下面按 reference 大致列一下它管理了哪些方面。
1. code-change-boundary:先管住手,不随意改动
这个 reference 主要强调最小改动原则。也就是说:任务要求改什么,就改什么;不要因为“更干净”“更通用”“更优雅”“更符合最佳实践”而扩大更改范围。
这条非常关键。因为 AI 很容易把“解决问题”理解成“顺便把附近看不顺眼的地方都整一整”。但真实项目中,很多时候我们需要的是可控的小幅度变更,而不是一次兴致勃勃的局部装修。这条规则会提醒 Codex:你是来修复这个 bug 的,不是来给项目重新投胎的。
2. global-frontend:中文、编码以及前端全局边界
这个 reference 管理的是全局前端开发规则,尤其是编码和中文字符处理。它要求读写文件前先确认编码,不要默认使用可能有问题的读取方式;中文要直接书写成中文,不要使用 Unicode escape;如果发现乱码或混合编码,要先解决编码问题。
这对中文项目特别重要。毕竟中文乱码不单纯是 bug,它更像一种仪式——一旦触发,项目里所有中文字幕都会开始怀疑自己的来源。
除了编码,它还强调不要修改无关代码、不要格式化无关内容、不要随意改动命名、props、CSS class 等。一句话:前端可以改,但不能趁机“大扫除”。
3. api-integration:不要猜测接口,严格按定义来
这个 reference 专门负责 API 接入。它的核心态度很明确:API 字段必须严格来自真实定义。不要猜测字段名,不要臆想响应结构,不要添加别名,也不要写成 a || b || c 这种自动兼容模式。如果字段不清晰,就去确认接口定义。
这正好命中了 AI 的常见毛病:太喜欢替后端“考虑周全”。但业务代码里,接口契约不是开放式作文。后端返回什么,前端就按什么接入。否则所谓的兼容会慢慢演变成技术债,最后谁也不知道哪个字段才是真正的依据。
4. backend-business-logic:业务逻辑别到处流浪
这个 reference 专注于后端和业务逻辑的边界。它强调业务规则应该待在真正属于它们的位置,不要分散在 controller、service、job、mapper、serializer 各处。
这也是 AI 容易犯的错。哪里需要判断,就在哪里补一段;哪里缺个转换,就在哪里加一个 helper。短期来看问题解决了,长期来看业务规则开始到处流浪。以后要修改一个状态判断,可能要在五个地方寻找它的分身。这条规则正是在提醒 AI:业务逻辑不是贴纸,不能哪里空就贴哪里。
5. frontend-implementation:小组件别写成小宇宙
这个 reference 关注前端实现。它强调可读性优先,不要为了优化引入复杂度;组件应当小巧而聚焦;是否抽取 hook、utils,要依据实际复杂度,而不是主观觉得“这样更优雅”。
这对 AI 很有必要。因为 AI 非常擅长把简单事情做得很完整。一个本来只在当前组件中用一次的逻辑,它可能会抽成 hook;一个简单 formatter,它可能会塞进 utils;一个局部状态,它可能会设计成可复用方案。看起来很专业,但专业不等于复杂。尤其在前端代码中,很多时候“放在这里一眼能看懂”比“抽出去需要点进去看”要好得多。
6. state-management-boundary:状态该归谁,就归谁
这个 reference 负责状态边界管理。它区分了组件状态、hook 状态、store 状态、URL query 状态和 server state。这能解决一个很常见的问题:AI 喜欢把状态放到“看起来更通用”的地方。
本来只属于某个组件的展开状态,被放进了 store;本来应该体现在 URL 上的筛选条件,被藏在了内部 state;本来属于服务端缓存的数据,被复制成多份本地状态。最终状态之间互相看不顺眼,页面刷新、返回、切换 tab 都可能出现奇怪行为。这条规则就是在告诉 Codex:状态不是越全局越高级,放到正确的地方才是高级。
7. side-effects-and-subscriptions:副作用要有生命周期
这个 reference 负责管理事件监听、订阅、定时器、watcher 等副作用。它强调注册要清晰,清理要明确,生命周期要可见。
AI 写副作用代码时,很容易只关心“让它生效”,而忘了“让它结束”。比如添加了 resize listener 却没有清理,比如开启了 timer 却不处理卸载,比如 watcher 和事件订阅重复触发同一段逻辑。这类问题短期不明显,但跑久就会变成难以排查的行为异常。这条规则相当于在提醒 AI:不是所有副作用都能靠缘分来收尾。
8. constants-and-magic-values:别让魔法值满地跑
这个 reference 负责管理常量和魔法值。比如业务状态、事件名、缓存 key、storage key、路由、API path、正则、默认值、分页数等。这些值如果只有局部意义,可以留在局部。但如果代表共享的业务含义,就应该有稳定的来源。
AI 有时会随手写出字符串:status === 'success'。单独看没问题。但如果项目里到处都是 'success'、'SUCCESS'、'done'、'finished',以后改业务状态时就会很热闹。这条规则的意义在于:有业务含义的值,别让它自己满项目乱跑。
9. type-convergence:类型相同,不代表概念相同
这个 reference 管理类型收敛。它会提醒 Codex:如果多个类型字段相同,且业务含义与生命周期也相同,可以考虑统一;但如果只是结构长得像,就不要强行合并。
这很重要。因为 AI 很容易看到两个 interface 字段一样,就觉得可以抽出公共类型。但前端表单类型、接口响应类型、数据库实体类型,可能字段一模一样,语义却完全不同。强行合并后,一处变动会迫使另一处跟着变,最终类型系统从保护伞变成了连坐制度。
10. convergence-and-abstraction:不是所有重复都该抽象
这个 reference 负责管理代码收敛和抽象边界。它承认一个现实:允许合理的局部重复。如果逻辑只出现一次,或者业务含义不同,或者未来变化方向不同,或者抽象会让代码更难懂,那么就不应该为了“消灭重复”而抽象。
这条规则非常反 AI 的本能。AI 看到重复,常常想收敛;看到相似,常常想封装。但抽象不是奖励机制,不是写得越多越高明。好的抽象应该降低理解成本,而不是把简单的代码变成参数迷宫。
11. ui-style-convergence:UI 风格可以统一,但别硬统一
这个 reference 负责管理 UI 样式与变体的收敛。例如相同按钮、输入框、卡片、徽标、表格样式,重复的 class list,暗黑模式规则,响应式布局规则等,都可以考虑收敛到基础组件、设计 token 或 variant。但它并不是鼓励 AI 一看到 class 重复就马上抽组件。
UI 中的一些重复来自设计系统,另一些来自局部业务场景。前者值得收敛,后者未必。所以这条规则的价值在于:让 AI 既能识别真正的设计重复,又不要随意把每个长相相似的拉去认亲。
12. engineering-config-convergence:工程配置别各玩各的
这个 reference 负责管理工程配置的收敛。比如 TypeScript 配置、lint 配置、测试配置、构建配置、formatter、package scripts、依赖版本、环境变量 schema、发布流程等。这些地方一旦重复且不一致,就很容易出现“我在本地能跑,你那里不行”的经典剧情。
但同样,它并不是叫 AI 随便改配置。工程配置的影响范围通常很大,所以只有当任务确实涉及配置收敛时,才应该去动它。换句话说:配置文件不是许愿池,不要路过就顺手扔一个优化进去。
13. dependency-management:不要为小事引入新依赖
这个 reference 负责管理依赖。它强调不要为了简单功能引入新依赖,不要引入职责重叠的第二个库,不要在窄任务里升级无关依赖,也不要随意修改 lockfile。
AI 有时非常喜欢推荐库。格式化时间?加个库。深拷贝?加个库。简单校验?加个库。但依赖不是免费的,它会带来体积、维护、安全、升级和团队认知成本。这条规则就是提醒 Codex:能用三行解决的问题,不要先召唤整个 npm。
14. docs-and-comments:注释解释为什么,不解释废话
这个 reference 负责文档与注释。它鼓励解释业务目的、分支原因、边界场景、状态流转、兼容约束、性能取舍以及临时方案的移除条件。但它反对写显而易见的注释,也反对没有 owner 或移除条件的 TODO。
AI 特别容易写出这样的注释:// 设置标题setTitle(title)。这不是注释,而是把代码又朗读了一遍。好的注释应该告诉人类:为什么这里要这么做,而不是告诉人类:这行代码具体长什么样。
15. testing:测试行为,不测试自我感动
这个 reference 负责测试。它强调不要给薄包装写无意义测试,不要重复相同输入输出的测试,不要测试 mock 本身,不要为了覆盖率数字而写测试。有价值的测试应该覆盖正常路径、边界值、空值、非法值、权限、状态转换、错误处理和回归场景等。
这点也非常适合约束 AI。AI 可以很快写出一堆测试,但数量不代表质量。如果测试仅仅在证明 mock 会返回 mock,那它更像是在给自己鼓掌。
16. error-handling-and-logging:错误不要被悄悄吞掉
这个 reference 负责错误处理与日志。它强调不要无声吞错,能明确失败就不要隐藏错误状态;处理错误要在真正能恢复的边界进行;转换或重新抛出错误时要保留有用的上下文。
日志也是一样。要记录能帮助诊断真实问题的信息,不要给正常流程增加一堆噪音,也不要把临时调试留到生产环境。这条规则的目的很朴素:出问题时,人类至少要知道问题在哪里,而不是只能对着控制台思考人生。
17. naming-and-files:命名稳定,文件别乱拆
这个 reference 负责命名与文件组织。它强调一个概念应该有一个稳定的名称,不同的名称应该意味着不同的含义;优先使用领域术语,不要随手起 helper、manager、common、base、util 这类模糊的名字。
它还提醒不要为了文件变小就拆文件,不要为了方便把无关职责塞进一个文件。AI 很容易“整理结构”,但结构整理本身也是改动。如果任务不要求,就不要顺手改命名、挪文件、拆模块。否则表面上只是组织调整,实际上可能影响导入路径、公共 API 和团队认知。
18. code-review:Review 先找风险,不先夸优雅
这个 reference 负责代码审查。它要求 review 时优先关注错误行为、回归、边界条件、契约不匹配、缺失验证、不安全副作用、测试缺失等。这可以避免 AI review 变成空洞的评价:“整体结构清晰。”“代码可读性较好。”“建议进一步优化。”
这些话不是完全没用,但如果不能指出真实风险,就像开会时说“我们要持续改进”一样,听起来正确,却难以落地。好的 review 应该先把可能出事故的地方指出来。
19. final-response:交付时说人话
这个 reference 负责最终回复。它要求 Codex 在完成任务后说清楚:改了什么,验证了什么,还有什么不确定,下一步做什么。这也很重要。因为 AI 很容易把最终回复写成过程汇报:“我首先分析了项目结构,然后检查了相关文件,接着进行了修改……”
用户真正关心的是结果。所以这条规则会要求它开门见山:事情是否完成,完成到什么程度,有没有验证,哪些地方还需要注意。
四、它真正解决的不是代码问题,而是边界问题
看完以上这些 reference,会发现 codex-dev-norms 真正解决的并不是某一种具体的 bug。它解决的是 AI 开发过程中的边界问题。
AI 很强大,但它不知道你的项目里哪些东西不能碰。它不知道这个接口字段必须严格按定义来,不知道这个重复现在不该抽象,不知道这个状态只属于当前组件,不知道这个中文文件不能随便用默认编码读写,它也不知道你今天只想修一个按钮,而不是顺便参加一次架构改造。
所以这套 skill 的意义,就是把这些隐含经验变成显式规则。让 Codex 从“能写代码”进化到“能按项目边界来写代码”。
五、让 AI 少一点自我发挥,多一点工程克制
AI 写代码的能力越来越强,这是不争的事实。但在实际项目中,能力强并不等同于可以随意发挥。很多时候,我们需要的不是一个永远有新点子的 AI,而是一个知道什么时候该停手的 AI。
codex-dev-norms 这类 skill,就是给 Codex 准备的一套开发家规。它不会让 AI 失去能力。恰恰相反,它让 AI 的能力变得更可控、更稳定、更适合融入真实的工程协作。
以前 Codex 像一个精力旺盛的搭子:你说一句,它能写一屏。现在有了这套规则,它依然能写一屏——但终于知道,该写的是哪半屏,不该写的又是哪半屏。
