AI 写代码越写越复杂?用 SKILL.md 把它按回 34 行
最近半年我最烦的不是需求评审而是眼睁睁看着 AI 写的代码越写越复杂。同一个接口第一版 30 行写完聊了三轮需求之后它能自己膨胀到 300 行里面还像模像样地塞了工厂、策略、模板、缓存三层抽象看着很专业改起来想哭。后来我把一个专门管代码复杂度的 Skill 挂进 Claude Code才勉强把这条上升曲线按住。这篇文章就是分享这个做法AI 为什么会写成这样Skill 为什么能治SKILL.md 到底怎么写实测效果和踩坑记录都放在下面。1. AI 写代码为什么会越写越复杂1.1 你看到的不是错觉复杂度是一条加速上升曲线先说个我自己的真实案例。上个月我让 AI 写一个简单的限流器第一版的时候它只写了 34 行一个字典存计数一个窗口判断完事。我当时还想这玩意儿反应真快。后来我在对话框里追加了几个需求无外乎“加个可配置的阈值”“支持多用户维度”“顺手做一下并发安全”。结果第二轮输出变成 86 行开始出现配置对象、接口定义、策略分离。到第三轮输出直接冲到 280 行硬生生塞进去工厂、策略模式、一个缓存层、还有看起来像是监控用的注解。最离谱的是等我把这 280 行删掉把里面真正的业务逻辑抽出来最后剩下的还是那 34 行能干的事。功能一个没少也没多。那 246 行是从哪儿来的不是需求带来的是生成过程自己在膨胀。我后来翻了一下类似的项目协作记录发现这不是个别现象。只要对话连续超过几轮代码体积每轮增长百分之二三十是非常常见的情况。并且这个增长不是线性增长而是越到后面越夸张——就像滚雪球前面还是两三层嵌套后面就开始往泛型、反射、AOP 这些方向跑了。版本行数典型结构V1 基本版34 行一个函数 计数器V2 加参数与并发86 行接口 配置对象 并发控制V3“健壮版”280 行工厂 策略 模板 缓存 注解对比完这几个版本我就想明白了一件事复杂度不是设计出来的是 AI“长”出来的。1.2 剖开现象大模型天生就有“长胖”的四个倾向为什么 AI 会一路往复杂里写我观察下来背后大概是四个天然倾向在共同作用。第一个倾向是生成长度偏好。大模型在训练阶段见过大量“写得详细、带防御逻辑、结构完整”的代码这类代码在训练数据里往往被标注成更专业、更像正确答案。于是模型在生成时会下意识地认为“输出越多、越完整越不容易被用户挑错”。说白了模型不是在写代码是在做一道越来越长的填空题。第二个倾向是防御式编程。模型被训练成尽量少被批评所以它宁可多写点空值校验、异常兜底、重试逻辑、默认参数也不愿意在某个边界情况上漏掉判断。问题是现实中 90% 的防御逻辑用户根本不关心。第三个倾向更隐蔽我管它叫企业架构幻觉。训练集里有大量源码是正经企业项目那些代码天然带有架构分层、接口抽象、依赖注入。模型不知道你只是一个个人工具脚本也不知道你不需要“可扩展到十几个下游系统”它默认把所有代码都按大厂标准来写。第四个倾向是对话约束衰减。你在第一轮说“保持简单不要过度设计”到了第 40 轮模型早就把这句话忘干净了。它只记得最近几轮用户提了更多需求、要求更健壮于是默认你需要的是一个全新的架构。这四个倾向加在一起导致一个必然结果单靠口头提醒是根本压不住复杂度的。提醒是一种瞬时记忆对话越长约束越弱。你会发现只要你不盯着AI 就会自己往复杂里钻。1.3 不治理的代价AI 让整个代码库的“平均复杂度”抬升有人可能会说代码复杂点就复杂点反正能跑就行。这话放在三五行的工具脚本上没问题放到一个团队长期维护的仓库里代价会以另一种形式爆发。我观察到一个很典型的恶性循环AI 生成的“看起来很健壮”的代码在评审时往往更容易被放行——因为它有错误处理、有设计模式、有注释显得很完整。于是代码库里开始堆积大量没有业务含义的抽象。同一个功能这个模块叫DataProcessor另一个模块叫DataHandler底层做的是同一件事。更麻烦的是长期阅读这种代码人本身的判断也会被带偏。你开始习惯“所有逻辑都要套接口”开始给只用一个实现类的接口写Impl开始在只有一种策略的场景里引入策略模式。等某一天你回过神想重构你会发现全代码库已经到处是这种“AI 脂肪”了。所以我的结论是如果团队开始重度使用 AI 写代码复杂度治理这件事就必须同步提上日程否则三个月后重构成本会成倍增加。而这件事靠口播约束靠不住需要一个制度化、项目级的东西来全程盯住这也是我折腾 Skill 的起点。2. Skill 是什么凭什么管得住复杂度2.1 一个文件夹就能让 Claude 自动改变行为先解释一下 Skill 到底是什么。我用的这个概念来自 Anthropic 推出的 Agent Skills中文常被叫作“技能”。它的本质非常朴素在项目里放一个目录目录里有一个 SKILL.md 文件这个文件描述一套规则Claude 在工作时只要识别到对应场景就会自动把文件里的规则加载出来按照规则执行。我以前管它叫“给 AI 贴墙上的操作规范”。普通提示词是你每次进房间都对着 AI 耳朵念一遍注意事项念完它记多少算多少。Skill 是直接把操作规范打印好贴在墙上AI 每次进来干活第一眼就能看到不需要你重复也不会像对话一样聊天聊长了就忘。具体到 Claude Code默认的约定是项目根目录里放.claude/skills/技能名/SKILL.md或者放到全局目录~/.claude/skills/技能名/。启动 Claude Code 之后它扫描到合法文件Skill 就自动生效了。你不需要额外写什么触发器AI 会根据 SKILL.md 里的描述在合适的场景主动去调用。这个机制最舒服的一点是它完全不依赖云端就是一个本地文件。你可以把 SKILL.md 放进 Git 仓库里团队拉下来普遍生效想改规则直接改文件重新提交就是了不用在每段对话里重新调教。2.2 Skill、提示词、Agent、MCP四兄弟别搞混这几年 AI 编程相关概念满天飞很多人把 Skill 和 Agent、提示词、MCP 混在一起。这里我直接给一张区分表这些年踩坑总结下来的最简版本概念一句话理解作用范围适合场景提示词 Prompt对话里的一次性嘱咐当前这段对话即兴、临时、一次性的需求Skill项目里的常驻操作手册项目级自动加载稳定的规则、流程、代码风格约束Agent能自己循环调用工具、有决策权的角色会话级多轮执行复杂任务拆解、自主执行链路MCP给 AI 提供外部工具连接的接口协议工具层连数据库、调 API、读文件系统如果只针对“管复杂度”这一个目标我的选择非常明确核心用 Skill必要时用 Agent 去调一个复杂度检查脚本。Prompt 太容易失效Agent 自由度太高容易发挥过头MCP 是解决工具连接问题的不适合直接约束生成行为。Skill 的定位特别适合“稳定守规矩”这件事。它不聪明它就是拿一套写死的规则给你兜底。而代码复杂度管理恰恰是最不需要 AI 即兴发挥的场景之一。2.3 为什么把“管复杂度”做成 Skill 最合适我试过用 Prompt 约束例如在每轮对话里加一句“记得控制行数保持简单”刚开始有用聊到后面该忘还是忘。我也试过用 Agent 来管结果它为了“完成目标”自己先写了一大堆分析文件最后还把规则改了——显然这不是我想要的。Skill 对这种场景的优势有四个。第一规则稳定。它不靠模型短短的上下文记忆而是每次生成前都会重新加载。只要对话不结束Skill 里的内容就在上下文里不存在“聊多了忘了”的问题。第二可复用。同一份 SKILL.md 可以直接从我自己的个人仓库复制到公司的业务仓库所有规则一杯端走。第三可版本化、可审阅。Skill 是文件意味着可以走 Git、可以 code review、可以在 CI 里做静态检查。你可以看到规则改了哪一行谁改的为什么改。第四规则聚焦。老话说得好把大象关进冰箱需要三步但把规则塞给 AI 只需要一个文件夹。Skill 不像 Agent 那样有大把自由它就只负责在某类任务触发时把“硬规矩”顶上去不会自己给自己加戏。要知道代码复杂度管理的本质不是让 AI“想明白”而是让它“别忘记”。Skill 恰好干的就是这个活儿。3. 手把手我是怎么写这个复杂度治理 Skill 的3.1 先从失控现场提炼四条铁律我在动手写 SKILL.md 之前没急着列一堆宏大规则而是先把自己觉得最痛的四条问题拎了出来。从那些失控的代码现场反推规则效率远高于凭空想象。第一条单函数不超过 50 行嵌套不超过 3 层。这是最直观的复杂度红线不可商榷。AI 最爱干的事就是写一个两百行的函数里面套四五层 if-else再塞两个 try-catch。这条规则直接对冲这种行为。第二条动手前先给方案大改动必须等待确认。凡是超过 200 行的新增或重构AI 必须先输出一段方案告诉我它会改哪些文件、不会动哪些东西等我点头再写。这一条能挡住很多“为了健壮而健壮”的无效大改。第三条每次改动必须说清楚删了什么、简化了什么。AI 习惯性保留冗余代码经常改完几轮之后代码里残留一堆没人调用的旧类、旧分支。要求它每轮列一次“删除清单”等于强制它自己检查这些冗余。第四条重复代码先抽函数禁止为未来业务凭空造抽象。一个抽象类至少要有两个现实使用者才值得存在。AI 特别喜欢给只有一个实现类的接口写Impl这种抽象不仅没带来好处还平添一整套跳转成本。有人可能会问为什么就这四条而不是一套完整的代码规范我的理由很简单复杂度治理不是能力问题是习惯问题。给 AI 太多规则它会搞不清优先级给少而硬、能当场验收的四条它反而记得住。3.2 SKILL.md 的结构和关键写法有了规则接下来就是把规则落进 SKILL.md。这是我的目录结构code-complexity/ ├── SKILL.md ├── scripts/ │ └── check_complexity.py └── references/ └── bad_vs_good_examples.mdSKILL.md 的核心逻辑分三段触发条件、硬规则、输出要求。我直接放一个可用的精简版--- name: code-complexity-handler description: 当用户要求生成、修改或重构代码时强制保持低复杂度并给出清晰的方案与变更说明。 --- # Code Complexity Handler ## 触发条件 - 用户要求“写代码”“实现功能”“重构”“修复 Bug”“优化”等与代码相关的任务。 - 上下文里出现需要生成或评价的代码时。 ## 硬规则 1. 编写任何代码之前先输出一段不超过 5 行的方案说明“这次会改什么、不会改什么”。 2. 不为还不存在的未来需求添加任何抽象层。新增接口或类必须至少有两个现成的真实使用场景。 3. 单个函数不超过 50 行嵌套不超过 3 层。如果超出必须主动拆分并给出拆分理由。 4. 出现重复代码时先抽函数或工具函数严禁通过再套一层抽象来“整合”。 5. 每次修改后列出本次删除或简化的内容并解释“为什么这一段不写抽象层”。 6. 如果项目里有 scripts/check_complexity.py完成代码后必须运行它输出全部为 PASS 才算完成。 ## 输出要求 - 每个代码任务完成后额外输出 2 行以内的复杂度小结例如 “新增 3 个函数均在 50 行内删除冗余旧接口 1 个没有新增抽象类。”这里有一条经验值得单独说规则要写命令式不要写建议式。你在 SKILL.md 里写“请尽量控制行数”AI 大概率就会“尽量”到 80 行你直接写“单函数不超过 50 行嵌套不超过 3 层”它的执行率会高很多。模型对指令性语气的响应能力远强于劝导式语气这不是玄学是模型训练数据里的指令遵循特性决定的。除了 SKILL.md我还写了一个配套检查脚本。脚本本身逻辑不复杂就是读取目标代码文件统计每个函数的行数和嵌套层数一旦超限就输出 FAIL。这类脚本的价值在于它把“规则是否被遵守”变成了一个可验证的结果而不是依赖 AI 主观复读。3.3 挂载到 Claude Code三步走挂载过程没有太多玄学核心就是搞清楚目录。我建议先挂全局目录这样任何项目都能生效。第一步创建目录mkdir -p ~/.claude/skills/code-complexity第二步把 SKILL.md、scripts、references 放进去cp SKILL.md ~/.claude/skills/code-complexity/ cp -r scripts ~/.claude/skills/code-complexity/ cp -r references ~/.claude/skills/code-complexity/第三步启动 Claude Code随便让它写一个小功能观察它是不是先输出了方案、最后又输出了复杂度小结。如果这两件事都出现了说明挂载成功。如果是在项目内生效目录换成.claude/skills/code-complexity/放在仓库根目录就行。团队场景下把整个.claude目录提交到 Git 里是最省心的做法——每个成员拉下代码后Skill 自动到位不用每个人手动装。至于桌面客户端或者 API 集成目前 Claude Code 对 Skill 的支持最完整风险最低。VS Code 里如果装了官方扩展一样能触发仓库里的 SKILL.md。只要你用的是 Anthropic 官方的模型入口Skill 机制基本都是认的。3.4 实测对照同样的需求挂载前后长什么样拿实例说话。我给 AI 输入同一句话“把这 300 行爬虫重构成更稳健的版本。”没有挂载 Skill 的时候AI 大概用了 1 分钟输出了一份 150 行的重构方案里面出现了AbstractDownloader、RetryStrategy、FaultTolerantFactory三个新抽象看起来非常“企业级”。但说实话这个爬虫只有两个下载函数根本配不上这么多抽象。挂载 Skill 之后AI 先输出方案“本次改动集中在重试逻辑抽取和异常收敛不新增抽象类不改变对外 API。预计删除 180 行。”然后列出了具体的改动点最后写出的代码拆成了几个 20 行左右的函数重试逻辑用一个带参数的普通函数解决没有再套策略类。关键在最后那一行AI 主动写了这样一句完成。新增代码 60 行删除 182 行没有新增抽象类。重试次数作为参数传入而不是做成策略对象因为当前只有一种重试策略实例化类没有收益。这就是 Skill 生效的样子。它不保证 AI 每次都能写出最优解但它强制 AI 对自己的每个复杂度决策都给一个理由。没有理由的抽象自然就被拦住了。4. 实测结果与问题排查实录4.1 三类难治场景和对应处理办法跑了一段时间之后我发现规则不是装上就能一劳永逸有几类场景仍然会让 Skill 失灵。第一类是走形式。AI 在开头的方案里写得漂漂亮亮说“我将移除不必要的抽象”但实际输出的代码里该有的抽象一个不少。我后来在规则里加了一条方案必须包含预计删除的行数如果整轮改动没有删除任何行必须明确说明为什么保留。把“必须删点什么”写进规则之后走形式的情况大幅减少。第二类是聊到一半又长胖。长对话里即使 SKILL.md 一直被加载模型也可能在几十轮后对规则产生“审美疲劳”开始默认新的需求需要动架构。我的应对方式是给 Skill 加一个快捷触发命令我起名叫cch输入/cch时 AI 会对当前代码生成一份复杂度报告并且直接列出可精简的点。相当于给它在对话中设置一个“重新校准”的按钮。第三类是存量代码已经烂掉了。Skill 对历史代码无能为力它只管“接下来怎么写”不会主动去清理旧账。我的处理办法是触发一个审计模式让 AI 先不要动手只输出一份“复杂度审计报告”用表格列出坏味道然后我再让 AI 按条目逐条修。千万不要对它说“整个项目重构一下”那会让 AI 在巨大的工作量面前又写出一堆新抽象来。4.2 常见问题速查表这些坑我都是真实踩过的整理成一张表遇到问题直接对号入座。症状原因解决办法AI 完全忽略 Skill 中的规则Claude Code 版本过旧或 SKILL.md 位置/格式不对升级 CLI 到最新版本检查目录是否为.claude/skills/技能名/SKILL.md确认 frontmatter 里 name 和 description 都存在多个 Skill 同时触发规则互相打架不同 SKILL.md 的 description 写得过于相似在 description 里加清晰的触发限定比如“仅当涉及代码生成或重构时”规则太严AI 直接拒绝写代码规则之间互相冲突或某条规则覆盖场景太宽减少规则条数保证每条规则只针对一类具体行为代码是简短了但错误处理全没了规则过度追求精简AI 把必要的校验也砍了补充一条例外规则允许必要的错误处理但必须把逻辑收进高层函数不散落嵌套Skill 在长对话中逐渐失效上下文过长导致模型注意力被稀释使用/cch重新加载复杂度检查流程或直接新开会话再继续4.3 避坑心得规则怎么写AI 才真的会听最后说几条我在反复调校中总结出的写法心得这比任何大而全的规范都实用。第一句句命令式不要客气。写 SKILL.md 时“可以的话请控制行数”效果是零直接写“单函数不超过 50 行”才有执行力。命令式语气对模型来说更像一个高优先级的 system 指令。第二每条规则都必须可核查。“代码尽量简洁”这种话模型没法检查自己有没有做到所以它也不会真做。但“单函数不超过 50 行”“嵌套不超过 3 层”这些是可计算、可验证的代码写出来就能数。规则一旦可核查AI 就绕不过去。第三一次只加一条规则跑几轮对话验证再增加。我刚开始一口气写了 20 条规则看着很全面实际执行率很低。后来删到 6 条核心硬规矩反而效果明显。规则太多本质上是所有规则优先级都变低了。第四规则要学会用绝对词但要有边界。一个 SKILL.md 里如果到处都是 NEVER、ALWAYSAI 也会麻木。最好每条硬规矩都限定在“什么场景下适用”让它知道什么时候执行、什么时候不执行。第五给 AI 留一个收尾动作。强制它每次写完代码都自查一遍复杂度并把自查结果写出来。这个动作等于让 AI 自己充当自己的审查员效果比外部提醒稳定得多。我个人在实际使用中的体会非常直接Skill 最大的价值不是让 AI 变聪明而是让 AI 稳定守规矩。AI 写代码长胖这件事本质上就是缺少一套可复制的复杂度预算。把预算做成 Skill 放进项目它就很难绕过去。我现在接手每个新仓库第一件事就是把精简版 SKILL.md 丢进.claude/skills目录。另外这个能力完全可以继续扩展比如让 Skill 在每次 PR 时自动跑复杂度检查、输出“删除建议”变成自动代码评审员这个方向我觉得很值得继续折腾。