ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI编程助手过度工程破解:ruleset与YAGNI实战指南

AI编程助手过度工程破解:ruleset与YAGNI实战指南 1. 那个让所有 AI 编程助手都翻车的经典场景你让 AI coding agent 帮你写一个读取 CSV 文件并打印前 5 行的小脚本。三秒后它给你返回了 200 行代码一个抽象工厂模式、一个配置管理器、一个日志装饰器、一套异常处理体系外加一个单元测试文件。你盯着屏幕心里只有一个念头——我只是想读个 CSV 而已。这不是个例。几乎所有深度使用过 AI coding agent 的人都经历过这种杀鸡用牛刀的荒诞感。你提的需求越简单它给你的方案越复杂。你越强调简单点它越觉得你在考验它的架构能力。这个现象有个专门的说法叫过度工程Over-engineering。它和 AI coding agent 的 ruleset 设计、训练方式、以及 YAGNI 原则的缺失都有关系。我前后用过好几款主流的 AI 编程助手也自己搭过基于大模型的代码生成流程踩过的坑足够写一本小册子。这篇文章就把这件事彻底拆开讲清楚为什么 AI coding agent 会系统性地倾向于过度工程背后的机制是什么以及你作为使用者能用哪些具体手段把它拉回正轨。先说结论AI coding agent 的过度工程不是 bug而是它训练目标和交互机制共同作用的必然结果。理解了这一点你才知道该从哪里下手去治它。2. 过度工程到底长什么样五种高频翻车模式在讲原因之前得先把过度工程这个模糊的词具体化。不然你说太复杂了AI 根本不知道你在说什么。我总结了自己遇到过的所有情况归纳出五种最高频的模式。2.1 抽象层数远超实际需要最典型的就是接口和实现的分离。你让它写一个函数它给你定义一个 Protocol 或者 ABC抽象基类然后写一个具体实现再写一个工厂函数来创建这个实现。三层结构就为了做一件把字符串转成大写的事。我实测过一个极端案例让某个 agent 写一个判断数字是否为偶数的函数它返回了带类型注解、带 docstring、带参数校验、带自定义异常类的 40 行代码。而正确答案是return n % 2 0。2.2 防御性编程过度到荒谬AI 特别喜欢加 try-except。一个纯内存计算、不可能抛异常的函数它也要包一层 try-except然后 log 一下再 raise 一个自定义异常。问题是这个自定义异常除了换个名字什么都没做。更离谱的是参数校验。你传进去的参数类型是明确的它还要在函数体里再检查一遍if not isinstance(x, int): raise TypeError。这种校验在静态类型语言里是编译器干的活在动态语言里也往往是调用方的责任但 AI 就是忍不住要加。2.3 配置化一切你让它写一个发送邮件的函数它给你搞出一个配置字典里面有 SMTP 服务器、端口、超时时间、重试次数、重试间隔、日志级别……然后告诉你这样更灵活以后改配置不用改代码。问题是这个项目里只有一处地方发邮件而且这辈子都不会改。这种为未来可能的扩展预留空间的思维正是过度工程的温床。2.4 引入不必要的依赖和设计模式写个简单的状态管理它给你上观察者模式。写个数据转换它给你上责任链模式。写个定时任务它建议你引入 Celery。你只是想跑个脚本它给你搭了一套微服务架构。我见过最夸张的是让 AI 写一个每天备份一次数据库的脚本它建议用 Kubernetes CronJob 加 Helm Chart 来管理。兄弟我连 Docker 都没装。2.5 测试和文档的过度膨胀这个比较隐蔽但同样烦人。你让它写一个函数它顺手给你写了 8 个单元测试覆盖了各种边界情况其中一半的测试用例在现实中根本不会出现。然后还给你写了一份 50 行的 docstring把每个参数、每个返回值、每个异常都详细描述了一遍。代码本身 10 行测试和文档 100 行。这种比例失衡在 AI 生成的代码里非常常见。注意这五种模式经常同时出现。一个 AI 生成的简单函数往往同时具备抽象层、防御性代码、配置化、设计模式和过度测试。这就是为什么你看到它的时候会觉得这不是我要的东西。3. 为什么 AI 会系统性地过度工程四个底层机制知道了现象接下来要挖根因。我花了很长时间研究这个问题也看了不少关于 RLHF基于人类反馈的强化学习和代码生成模型的资料。总结下来有四个机制在共同作用。3.1 RLHF 训练把全面和专业划了等号这是最核心的原因。AI coding agent 的训练过程本质上是在优化一个奖励信号。而这个奖励信号来自人类标注者的偏好。问题就出在这里当人类标注者面对两个回答时更容易给更全面、更详细、更周到的那个打高分。一个只返回return n % 2 0的回答和一个返回 40 行带完整错误处理的回答标注者往往会觉得后者更专业更用心。这种偏好被模型学去了。模型逐渐形成一种认知输出越复杂、越全面、越显得考虑周到就越可能得到高分。于是它开始系统性地过度工程。它不是不知道简单方案而是它判断简单方案不够好。这就像一个新入职的员工为了表现自己能力强把每个任务都做得特别复杂。他不是不会简单做而是他觉得简单做显得自己没价值。3.2 训练数据里全是教科书式的代码AI 的代码能力来自海量代码库的训练。而这些代码库里占比最高的是什么是开源项目、教程、技术博客、Stack Overflow 回答。这些内容的共同特点是它们展示的是最佳实践而不是最简实践。一个教程教你写 HTTP 请求它会给你完整的错误处理、重试逻辑、超时设置。一个开源项目里的工具函数往往带着完整的类型注解和文档。但真实项目里的代码大部分是够用就行的。那些随手写的脚本、临时性的工具、一次性的数据处理代码很少被上传到 GitHub也很少被写成教程。所以 AI 看到的训练数据是经过筛选的、偏向正式和完整的代码。它没见过多少糙快猛的代码自然也就不会写。3.3 交互机制鼓励一次给全你和 AI coding agent 的交互方式也在推波助澜。你提一个需求它给你一个回答。这个回答是一次性的它没有机会在后续对话里逐步补充。所以模型的策略是既然只有一次机会那就把所有可能需要的都给你。它不知道你后面会不会需要错误处理不知道你会不会改配置不知道你会不会扩展功能。为了保险它全给你加上。这就像你去餐厅点菜服务员不知道你饭量多大干脆给你上一桌。你吃不完是你的事但它保证了不会不够。3.4 YAGNI 原则在 AI 的价值观里几乎不存在YAGNI 是 You Arent Gonna Need It 的缩写意思是你不需要它。这是极限编程里的核心原则不要为未来可能的需求提前写代码因为大部分可能都不会发生。人类资深工程师信奉这个原则因为他们被现实毒打过太多次——提前抽象的东西最后往往用不上还成了维护负担。但 AI 没有这种被毒打的经历。它的训练数据里YAGNI 的讨论远少于设计模式、架构原则、最佳实践的内容。所以在 AI 的价值观里为未来预留空间是美德够用就行是偷懒。它天然站在过度工程那一边。4. ruleset 才是你手里最有效的刹车理解了原因解决方案就清晰了。你不能指望 AI 自己想通但你可以通过 ruleset规则集来约束它的行为。这是目前最有效、最可控的手段。4.1 ruleset 的本质给 AI 的项目宪法ruleset 就是你预先写好的、告诉 AI在这个项目里应该怎么做、不应该怎么做的规则文件。不同工具叫法不同有的叫 system prompt有的叫 custom instructions有的叫 rules 文件。但本质一样在 AI 开始干活之前先把你的偏好和约束灌给它。很多人用 AI coding agent 是裸用的——打开对话框直接提需求没有任何前置规则。这种情况下AI 只能用它自己的默认价值观来判断而它的默认价值观就是越全面越好。一旦你写了 ruleset情况就完全不同。你等于在它动手之前先给它立了规矩。4.2 一份能治过度工程的 ruleset 长什么样我试过很多版本的 ruleset最后沉淀出一套比较有效的。核心思路是用明确的、可执行的规则去对抗 AI 的默认倾向。下面是我实际在用的版本你可以直接抄。# 代码风格规则 ## 核心原则 - 优先选择最简单的实现方案能一行解决的不写两行 - 严格遵守 YAGNI不要为未来可能的需求写代码 - 不要引入当前需求用不到的抽象层、设计模式、配置项 ## 具体约束 - 函数不超过 20 行超过就说明该拆或者该简化 - 除非明确要求不写自定义异常类直接用内置异常 - 除非明确要求不加 try-except让错误自然抛出 - 除非明确要求不做参数类型校验信任调用方 - 除非明确要求不写 docstring函数名和参数名要自解释 - 除非明确要求不写单元测试 - 除非明确要求不引入新的第三方依赖 - 不要用工厂模式、观察者模式等设计模式除非我明确要求 ## 判断标准 在给出方案前先问自己这个代码能不能更短这个抽象是不是现在就需要 如果答案是能更短或不是现在就需要那就简化。这份 ruleset 的关键在于具体。要简单这种话 AI 理解不了但函数不超过 20 行不加 try-except这种可量化的规则它能执行。4.3 ruleset 的放置位置和生效范围不同工具的 ruleset 放置方式不一样。有的支持项目级配置文件放在项目根目录有的支持全局配置对所有项目生效有的只能在对话里临时指定。我的建议是分层配置层级内容适用场景全局层通用的代码风格偏好所有项目项目层该项目的技术栈、架构约束单个项目对话层当前任务的特殊要求单次对话全局层放那些你永远不想变的规则比如不要过度工程。项目层放技术栈相关的比如这个项目用 FastAPI不要引入 Flask。对话层放临时性的比如这次就写个一次性脚本怎么快怎么来。4.4 ruleset 不是写完就完事要迭代我一开始写的 ruleset 很粗糙就一句代码要简单。效果很差AI 该复杂还是复杂。后来我每次遇到过度工程的情况就把具体的反例加进 ruleset。比如有一次它给我写了个带重试逻辑的 HTTP 请求我就加了一条除非明确要求不写重试逻辑。又比如它老是给我加配置字典我就加了除非明确要求不把参数抽成配置。迭代了大概两个月这份 ruleset 才变得比较完善。现在我用它AI 生成的代码复杂度明显下降基本能控制在合理范围内。提示ruleset 的效果和模型能力有关。能力越强的模型对 ruleset 的遵循度越高。如果你用的模型比较弱可能需要把规则写得更直白、更强硬。5. 除了 ruleset还有哪些实操手段能压制过度工程ruleset 是基础但光靠它还不够。在实际使用中我总结了一套组合拳配合 ruleset 一起用效果最好。5.1 在需求描述里主动封顶你提需求的方式直接影响 AI 的输出。如果你说写一个读取 CSV 的函数它会给你一个完整的函数。但如果你说写一个读取 CSV 的函数不超过 10 行不要错误处理它就会收敛很多。我现在的习惯是在需求里直接给出约束用最简单的方式实现不要抽象这是一个一次性脚本怎么快怎么来不要写测试不要写文档代码控制在 XX 行以内这些约束相当于在对话层又加了一层 ruleset效果立竿见影。5.2 用减法指令而不是加法指令这是一个反直觉但很有效的技巧。大部分人提需求是加法——帮我加个错误处理帮我加个日志。但对付过度工程你要用减法。具体做法是先让 AI 生成然后明确告诉它删什么。比如它给你生成了 50 行代码你不要说简化一下它不知道简化到什么程度而是说删掉所有的 try-except删掉配置字典把参数直接写死删掉那个抽象类直接用具体实现。这种指令非常明确AI 执行起来不会跑偏。我经常用这招通常两三轮就能把代码砍到合理大小。5.3 让它先给方案再写代码过度工程往往发生在直接写代码的时候。AI 一旦开始写就会顺着自己的惯性往下堆。我的做法是先让它用自然语言描述方案我确认后再让它写代码。比如我会说先别写代码用三句话告诉我你打算怎么做。如果它的方案里出现了我会定义一个接口我会加一个配置层这种话我立刻就能发现过度工程的苗头当场叫停。这比等它写完 200 行代码再改要高效得多。5.4 用具体案例做 few-shot 引导如果你有现成的、风格符合你要求的代码直接丢给 AI 当参考。比如你说参考下面这个函数的风格帮我写一个类似功能的。AI 的模仿能力很强。你给它一个 5 行的简洁函数作为范例它生成的东西也会倾向于简洁。这比任何文字规则都管用因为它是用例子说话。我维护了一个自己的代码风格样例库里面放了几十个我写的简洁函数。需要 AI 写类似功能时就挑一个丢给它当参考。5.5 定期反向审查AI 的代码AI 生成的代码不要直接用。养成一个习惯拿到代码后先问自己这段代码里哪些是可以删的。我通常会做一次删除测试把那些看起来可能有用的部分删掉跑一下如果还能正常工作那就说明它们本来就是多余的。这个习惯坚持久了你会对过度工程越来越敏感甚至能在 AI 生成之前就预判它会往哪个方向膨胀。6. 一个完整的实战案例从 200 行砍到 15 行光讲道理不够我用一个真实案例把上面的方法串起来。这个案例是我帮朋友处理一个数据清洗任务时遇到的。6.1 原始需求与 AI 的豪华方案需求很简单读取一个 CSV 文件把其中某一列的空值行删掉然后保存回原文件。我把这个需求丢给某个 AI coding agent它返回了大概 200 行代码。结构是这样的一个CSVProcessor类类里有一个__init__方法接收配置字典一个load方法带完整的文件存在性检查、编码检测、异常处理一个clean方法带日志记录、进度条、多种空值判断逻辑一个save方法带备份、原子写入、异常回滚一个Config数据类一个main函数带命令行参数解析外加 6 个单元测试我朋友看完直接懵了。他要的就是个一次性脚本跑完就删的那种。6.2 用 ruleset 和减法指令逐步收敛我接手后第一步是加载我的 ruleset然后重新提需求。这次我加了一句这是一个一次性脚本用最简单的方式实现不要类不要配置不要测试。AI 第二次返回了大概 40 行。还是有 try-except还是有个main函数还是用了argparse。第二步我用减法指令删掉所有 try-except删掉 argparse文件路径直接写死在代码里删掉 main 函数直接顺序执行。AI 第三次返回了 15 行左右。基本符合要求了。第三步我手动看了一眼发现它还在用csv.DictReader而不是更简单的csv.reader而且空值判断写了个辅助函数。我直接说用 csv.reader空值判断直接内联不要辅助函数。最终版本是这样的import csv input_file data.csv output_file data_clean.csv with open(input_file, r, encodingutf-8) as f: reader csv.reader(f) rows [row for row in reader if row[2].strip()] with open(output_file, w, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerows(rows)15 行功能完全一样。从 200 行到 15 行砍掉了 92%。6.3 这个案例暴露的三个关键点第一AI 的默认输出和你的实际需求之间差距可能是数量级的。200 行和 15 行不是稍微复杂一点而是完全不同的东西。第二收敛过程需要多轮但每轮都要有明确的指令。你不能指望一句话就让 AI 从 200 行跳到 15 行。但你可以通过加约束 减法指令的组合一轮一轮逼近目标。第三最终判断还是要靠人。AI 收敛到 15 行后我还是手动检查了一遍发现了两处可以进一步简化的地方。AI 能帮你砍掉大部分冗余但最后那 10% 的精简往往需要人的判断。7. 不同场景下的策略差异不是所有代码都该极简讲到这里必须澄清一个误区过度工程的反面不是越简单越好而是恰到好处。不同的场景对复杂度的容忍度是不一样的。如果你把极简当成教条那又会走向另一个极端。7.1 一次性脚本能跑就行这类代码的生命周期可能只有几小时跑完就删。这种情况下任何抽象、任何错误处理、任何测试都是浪费。我的策略是ruleset 拉满减法指令用到底不写任何以后可能用得上的东西。文件路径写死参数写死不处理异常让它崩就崩。7.2 长期维护的项目代码适度工程是必要的如果是会长期存在、多人协作的项目代码那适度的抽象、错误处理、测试就是必要的。这时候过度工程的定义会变化——不是有抽象就是过度而是抽象层级超过了实际需要。这种情况下我的策略是ruleset 保留核心的 YAGNI 原则但放宽对抽象和测试的限制。让 AI 写测试但要求它只覆盖核心路径不追求 100% 覆盖率。7.3 原型和验证性代码快速优先做技术验证或者原型的时候目标是尽快验证想法是否可行。这时候代码质量不重要能跑通就行。我的策略是明确告诉 AI这是原型不要考虑代码质量怎么快怎么来。有时候我甚至会说允许你写脏代码。7.4 不同场景的策略对照场景抽象错误处理测试配置化ruleset 强度一次性脚本禁止禁止禁止禁止最强长期项目适度必要核心路径按需中等原型验证禁止禁止禁止禁止最强库/SDK必要必要完整必要最弱这张表的核心意思是你对 AI 的约束强度应该和代码的预期寿命、使用范围成反比。代码活得越短、用得越窄约束就该越强。8. 那些我踩过的坑和总结出的经验最后分享一些零散的、但很实用的经验。这些都是我在实际使用中踩坑踩出来的常规文档里不会写。8.1 ruleset 写太长反而失效我一开始以为 ruleset 越详细越好写了 200 多行。结果发现 AI 经常选择性失忆前面的规则记得后面的就忘了。后来我精简到 30 行左右只保留最核心的规则效果反而更好。ruleset 的关键不是全而是准。把最重要的几条写清楚比写一大堆它记不住的规则强。8.2 不要过度工程这句话本身没用我试过直接在 ruleset 里写不要过度工程完全没用。因为 AI 对过度工程的定义和我不一样。它觉得自己写的 200 行是合理的工程实践不是过度。有效的做法是把过度工程翻译成具体的、可执行的行为约束。比如函数不超过 20 行不加 try-except不写自定义异常。这些它能理解也能执行。8.3 不同模型的过度工程倾向差异很大我用过的几个模型里有的特别爱过度工程有的相对克制。这个差异和模型的训练方式、RLHF 的偏好数据都有关系。我的经验是如果一个模型怎么调都改不掉过度工程的毛病那就换一个。与其花大量时间跟它较劲不如换一个天生就更克制的模型。ruleset 能改善但改不了根本倾向。8.4 定期回顾 AI 生成的代码建立自己的反模式清单我有个习惯每隔一段时间就回顾一下 AI 最近生成的代码把那些过度工程的模式记下来。比如又给我加配置字典了又用工厂模式了又写自定义异常了。这份清单积累到一定程度我就把它转化成 ruleset 里的具体规则。这样 ruleset 会越来越贴合我的实际需求效果也越来越好。8.5 别忘了 AI 也在进化ruleset 要跟着更新AI 模型更新很快新版本的行为可能和旧版本不一样。我遇到过好几次模型升级后原来好用的 ruleset 突然失效了因为新模型对某些规则的理解变了。所以 ruleset 不是一劳永逸的。每次模型大版本更新后我都会重新测试一遍看看哪些规则需要调整。8.6 最重要的一条你自己得先想清楚要什么说了这么多技巧但最根本的一条是你得先想清楚自己要什么。AI 过度工程很多时候是因为你没说清楚。你说写个函数处理数据它不知道数据多大、处理逻辑多复杂、要不要错误处理只能按最全的来。但如果你说写个函数把 CSV 里第三列为空的行删掉数据量不超过 1 万行不需要错误处理它就能给你一个精准的方案。清晰的输入才有精准的输出。这个道理在 AI 时代依然成立甚至比以前更重要。因为 AI 的执行力太强了你指错方向它会以极快的速度跑偏很远。我在实际使用中最大的体会是把 AI coding agent 当成一个能力很强但缺乏判断力的初级工程师。它什么都能做但它不知道什么该做、什么不该做。你的工作就是通过 ruleset、需求描述、减法指令这些手段把你的判断力传递给它。这个过程需要耐心也需要你自己对什么是好代码有清晰的认识。一旦你建立起了这套约束体系AI 的生产力才会真正释放出来而不是被它自己的过度工程拖累。
返回列表