news 2026/9/5 11:14:42

AI写代码总过度设计?用KISS和YAGNI原则让它输出简洁代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI写代码总过度设计?用KISS和YAGNI原则让它输出简洁代码

帮我在终端里让AI写个解析环境变量的函数,规则是KEY=value分行解析,忽略空行和#注释。大概三秒后,agent交回的答案是:一个EnvParser接口、一个DefaultEnvParser实现、一个ParserFactory工厂类,工厂内部还加了缓存逻辑,签名里带了个“以后没准能用上”的ignore_unknown_keys参数。

功能当然能跑,测试也全绿。可我只想要一个函数。

这个画面,2025年还在用AI编程的人应该都不陌生。问题根本不在于agent能不能写代码,而在于我们想表达的“编程风格朴实简洁、防止复杂化、强化易读性”,在软件工程的专业语境里到底叫什么、用什么词去描述、怎么把它变成一条AI agent能稳定执行的指令。这篇文章不聊空泛的“提示词技巧”,就把这件事从术语对齐、生成机理、Prompt模板、工程配置到代码评审一条线讲透。

1. 先确定你在提的到底是哪种需求:把“好读”翻译成工程术语

和AI协作最忌讳的一件事,就是直接用“代码写得朴实一点”“别搞太复杂”这种自然语言去下达工程指令。因为“朴实”“复杂”是感受词,不同人有完全不同的尺度。更麻烦的是,大模型不会追着你问清楚,它会按照它理解的平均值去执行,而它的平均值往往是偏重的。

在软件工程体系里,“编程风格简洁、防止复杂化、强化易读性”不是一个单一需求,它是一组非功能性需求(Non-Functional Requirements)。要传达清楚,至少需要拆成四个可被独立判断的维度。

1.1 简洁性(Simplicity)不等于简陋

软件工程谈简洁,通常落到KISS原则(Keep It Simple, Stupid)上:在所有能满足当前验收标准的方案里,选结构和逻辑最简单的那一个。

很多人担心“写简单了是不是显得水平不够”,这是被带偏了。系统设计领域里,一个公认事实是:新增一个概念、一个抽象层、一个间接调用,都会给后来的维护者增加理解成本。真正的KISS不是让你省略边界处理、省略错误捕获,而是不在没有收益的方向上增加结构。

举个典型例子,两个方案都能读取配置:

方案A是直接读文件、拆行、返回字典。 方案B先建ConfigSource接口,再写FileConfigSourceEnvConfigSource两个实现,然后通过ConfigLoaderFactory根据运行时参数决定装配哪个来源。

方案A一眼望穿,方案B看起来“优雅”,但如果当前根本不存在“多个配置来源”这个真实需求,B就是纯粹的过度设计。KISS要求的是A。

1.2 YAGNI:不要为想象中的未来买单

YAGNI全称是You Ain‘t Gonna Need It,出自极限编程(XP)领域。它的原始含义非常直接:永远不要为“你觉得以后可能会有”的需求写代码。

这一条恰恰是AI agent最常违反的。你在Prompt里说“实现一个用户登录接口”,模型内部会自动补全出一套“未来可能需要支持多种登录方式”的判断,然后顺手给你引入策略模式。你根本没说需要,它先替你设计了。

所以在软件工程评审中,对这类问题的标准术语叫“投机性泛化”(Speculative Generality),也叫“为未来预留抽象”。这是代码坏味道里非常经典的一种,Martin Fowler的Refactoring目录里就有专门条目。如果你的目标是“防止复杂化”,那YAGNI就是必须写进约束里的第一原则。

1.3 可读性:核心指标是认知负荷

可读性(Readability)是很虚的词吗?在工程实践里其实不虚。它有一个非常实用的解释:一个此前没看过这段代码的工程师,从零开始读懂它需要付出多少认知努力。这个努力程度在认知科学里叫认知负荷(Cognitive Load)。

降低认知负荷的操作包括:命名直接表达意图、函数短到能放进工作记忆、消灭多层嵌套、不依赖调用顺序做隐式状态传递。行数本身不是唯一标准,但一个超过40行的函数,对多数人来说已经很难一次在脑子里模拟执行完,这时候拆解往往是为了可读性而不是为了制造更多类型。

还有一个特别重要的点:可读性差的代码,往往靠注释找补。正宗的做法正好反过来——如果一段代码需要靠注释才能看懂,优先怀疑代码结构本身有问题,也就是代码整洁之道里反复强调的“命名和结构优先于注释”。

1.4 最小惊讶原则:让读者永远猜得中

最小惊讶原则(Principle of Least Astonishment)在UI设计里提得多,软件工程其实一样适用。它的意思是:一段代码的行为和读代码的人心里预设的行为尽量保持一致,不要整出“惊喜”。

比如一个函数叫load_config(),读者预期它可能返回配置对象;结果它内部还顺带改了全局状态、建立了网络连接。这会让所有调用方都活在恐惧中。放到AI生成代码的语境下,我们同样要约束agent不搞“花活”:不使用没必要的装饰器、不引入控制反转、不在常规数据流里埋隐式上下文。

1.5 一张表把“人话”翻译成agent能执行的规则

和同行沟通时可以直接说术语,但要让agent理解,最好每条都有一个“可观察”的行为准则。

你想要的中文表达软件工程术语落到agent身上的约束规则示例
“代码朴实点”Simplicity / KISS在满足全部验收标准的前提下,优先选择代码行数最少、调用链最短的实现方式
“别整那些用不上的抽象”YAGNI / No Speculative Generality禁止为“未来可能会出现”的第二个调用方预先设计接口、基类或工厂
“好读、一眼能懂”Readability / Low Cognitive Load单个函数默认不超过30-40行;命名让读者不需要注释就能说出意图
“别让读代码的人意外”Principle of Least Astonishment函数名只承诺它字面上的行为,不添加副作用与隐藏的全局状态变更
“不要提前做架构”Evolutionary Design等第二个真实需求出现时再做抽象,现在先用最直接的结构落地

2. AI 为什么会把代码越写越重:一套必然的“加戏”机制

很多人骂AI写代码啰嗦,其实根源不复杂。先理解大模型的生成逻辑:它不是在执行最优代码搜索,而是在预测一个“看起来最合理、最像优秀工程师会写出的token序列”。这个机制决定了两个倾向。

2.1 复杂化是模型的“概率默认值”

训练语料里,GitHub上那些大型开源项目、带有完整分层架构的代码库、各类设计模式教程占的比例远高于“三行写完一个功能”的极简片段。模型在预测下一个词的时候,对“接口+实现+工厂”这种组合模式的概率估计天然偏高,因为它见过太多次了。

可以类比一个刚看完大量“企业级架构课”的实习生。你让他写一个“加载配置”的任务,他脑子里最想展示的是自己刚学会的抽象能力,而不是交付一个刚好够用的函数。AI没有“展示欲”,但它的概率分布本身就是“重口味”的。

更关键的是,在主流代码能力评测里,功能正确性是硬指标,“多余抽象”几乎不受惩罚。RLHF阶段的人类标注员看到一份结构完整、包含接口设计的代码时,也确实更容易觉得它“专业”。于是在模型看来,多写抽象不仅无风险,甚至可能加分。这个先验只有靠用户的显式约束去压制。

2.2 Agent比普通对话模型更容易膨胀

普通ChatGPT生成代码时只是单轮响应,Agent则多了一层“任务规划”。我自己观察过不少次agent的思考过程:它会把一个大任务拆成子任务列表,然后逐个执行。问题就出在子任务列表上——模型拆任务拆到一半,经常自己补一个“考虑后续可扩展性”或者“预留配置接口”的子任务进去。

它为什么会这么干?因为“设计一个可扩展的架构”在训练数据里是被高度赞扬的行为。既然没有惩罚机制说“这个任务根本不需要扩展性”,它就会把这当成增值项。

还有一个隐蔽的因素是Prompt里的模糊带。当你说“实现一个配置管理”时,AI会自动把需求放大成“实现一个可以管理多种格式、支持动态重载、未来对接配置中心的配置管理子系统”。它不是坏,是过度补全。用户写的范围描述里没写明“不需要什么”,模型就会按“平均需求”来补全。

2.3 结论:不显式约束,轻松写就会被默认走高复杂度路线

所以我们必须接受一个现实:如果你想只用一个函数解决,却不在指令里明确“这是一次性代码,不为未来预留接口”,agent大概率会给你整出一个包着三层结构的模块。而反过来,只要你把“禁止引入接口层、禁止增加未使用配置参数”写成硬性规则,模型是完全有能力输出简洁代码的。

它的能力不差,差的是你定义验收标准的完整度。

3. 用结构化约束写 Prompt:让“简朴”不再是模糊感受

既然模型默认走复杂路线,我们的应对就是让Prompt带一套风格约束块。这套约束不是一句“请写简单点”,而是把上一章的术语翻译成可执行的规则。我实践下来,真正好用的约束条数是6到10条,太多了模型会稀释注意力,太少了又拦不住它加戏。

3.1 应该往Prompt里塞哪些约束

以下几类约束项是我最常用的,每一条都尽量让agent可以依据代码本身判断对错:

  • 约束范围:只实现当前列出的功能,不要为后续可能的需求做预留设计。
  • 禁止投机抽象:不引入当前代码中只有一种具体实现的接口、基类或工厂。
  • 选择偏好:在满足功能正确性的前提下,优先选择最短且直接的写法;标准库能力优先于自定义封装。
  • 结构限制:不要在单次实现中新增超过一个文件;新增类或函数必须在回复中说明它解决了当前哪个具体问题,答不上来就不允许引入。
  • 命名优先:变量、函数、文件命名以“未读过代码的人能猜出意图”为准。
  • 去掉死配置:不添加任何当前功能没有用到的参数、配置项、依赖;不要为一段简单逻辑单独开配置文件。
  • 交付前自查:代码完成后,按上述约束逐条检查,并在最后列出你删减了哪些冗余结构。

3.2 一个可直接复用的Prompt模板

我用得最多的模板长这样,可以直接复制改写:

# 角色 你是一名务实的中级工程师,不是架构师。写出让团队新人能直接接手维护的代码。 # 本次要做的功能 (在这里写清功能范围、输入输出、验收标准) # 硬性风格约束 1. 只实现上面列出的功能。不为将来可能出现的需求增加抽象。 2. 在同样满足验收标准的前提下,选行数最少、调用链最短的实现。 3. 除非当前功能确实需要多态/继承,否则禁止新增接口、抽象基类、工厂类。 4. 不新增没有被调用的参数、配置项、公共方法。 5. 新函数或新类必须先回答:它解决了当前哪个具体问题?答不上来就不要引入。 6. 变量、函数名必须让读者不需要注释就能看懂意图。 7. 不要在命名已经清晰的地方堆解释性注释。 8. 实现后自查:圈出所有可能被评审认为过度设计的位置,并说明为什么保留,或直接删除。

这个模板的核心是给Agent立一个“务实中级工程师”的人设。效果不是玄学,它把生成风格从“架构展示模式”切换到了“交付维护模式”,两种模式下模型产出的结构差异非常明显。

3.3 一次对照实验:约束前后差多少

拿最常见的一个任务来试:从一个多行文本中解析出路径和对应的文件大小。

不写风格约束直接问时,模型大概率会给一个PathEntry数据类,再配一个PathParser工具类,里面加一个parse静态方法,方法里再做try/except双分支处理不同格式,最后在控制器里调用。三层结构,四五十行。

把上面3.2的模板套上去后,模型给的是:一个普通函数,输入文本返回list[tuple[str, int]],十几行收工。该处理的空行、异常情况一样没少,只是没有包壳的层次感。

同一套功能需求,差一倍的代码量和三层的理解跳转,区别只在Prompt里有没有那几条硬约束。这说明约束是真实起作用的,不是心理安慰。

3.4 写Prompt时最容易失效的三句话

有几种表达方式我建议直接淘汰。第一句是“代码质量要高”,质量的定义太宽,模型只会加大它默认那套“高”的分量。第二句是“不要过度设计”,这句话有效但太弱,它没有给出具体的判断维度,模型不知道该砍什么。第三句是“尽量简单”,副作用是模型可能连必要的错误处理都省了,因为它把“简单”理解成了“少写边界代码”。

更有效的做法是在Prompt里写明“不做什么”的具体清单。如果Agent输出的代码还是复杂了,你就直接指出具体位置。比如:

“这个EnvLoaderFactory在当前需求下只有一个实现,删掉Factory层,让调用方直接创建EnvLoaderignore_unknown_keys参数目前没有任何调用方,删掉之后跑一遍测试。”

这种反馈每次都作用在具体的类、参数和行上,而不是停留在“太多太复杂”的抽象层面。迭代三四次之后,agent会越来越熟悉你的标准,因为它实际上是在通过你的纠正做少样本学习。

4. 把规则沉淀成项目级配置:一次声明,让 agent 每次自动带上

单独一轮写Prompt的约束再强,也敌不过时间。过两天新开一个会话、换一个agent工具,风格又回到默认值。所以要把规则写进项目级别的配置文件里,让它成为agent每次进入项目都能看到的“工作手册”。

4.1 不同工具读的是哪些规则文件

目前主流AI编程工具的配置方式差异较大,而且迭代很快,以下信息以写这篇文章时的主流版本为准,新版本建议查官方文档:

工具规则文件说明
Cursor.cursorrules.cursor/rules/*.mdc项目根目录下创建后,每次会话自动加载
Claude CodeCLAUDE.md支持项目级和子目录级指令,agent会主动读取
Continue.continue/config.json里的rules字段也可以让规则指向外部文档
Devin / 类似云端Agent项目根目录的AGENTS.md或平台级偏好设置按各平台的说明为准
GitHub Copilot agent模式.github/copilot-instructions.md会让代码补全与聊天中的agent都参考该文件

你不需要每个文件全建,选自己主力工具对应的即可。这套文件的意义是:项目风格约束从“每次临时口述”变成了“版本管理的一部分”,随代码一起进Git仓库,团队所有人都能复用。

4.2 一份可以在仓库里落地的最小规则示例

我习惯把它命名为AGENTS.md或者在Cursor中叫.cursorrules,内容控制在十行左右,让模型每次轻松吞下而不是被长文冲淡注意力:

# 工程风格准则(AI协作版) 这个仓库的所有AI生成代码必须遵守以下规则: 1. 每次改动遵循最小变更原则:完成Issue要求即可,不做无关重构。 2. 不为“未来需求”增加接口、抽象基类或Factory。只有出现第二个真实调用方时,才允许抽象。 3. 新增任何新的依赖、配置文件、目录结构,必须显式说明用途并等待确认。 4. 函数目标控制在40行以内,优先用清晰的命名而不是注释表达意图。 5. 如果一个类只有一个真实使用点,优先考虑合并到调用方。 6. 所有新公共API必须附带一个真实场景可以执行的示例,而不是空泛的文档。 生成代码后,请检查输出是否违反了上述任意一条。如果违反,请主动修改后输出。

这些条款写得越像“工作时审核标准”,模型执行越稳定。因为它读到的不是情绪化的“要简洁”,而是一组可以被代码本身验证的客观条件。

值得提醒的是,规则文件不是越多越好。我见过有人把一份长达200行的“规范大全”塞给AI,实际效果反而变差——大模型在上下文中对长文件的注意力有限,规则太长,后面十几条就变成了摆设。宁可保留最频繁违反的十到二十条,定期往里补一条“最近的教训”,也不要一次堆满。

另外一个细节是:持续会话过程中,最有效的规则其实是“你上一轮接收到的纠正”。如果agent在单次对话里被你纠正过“不要Factory”,它后续遵守得很好;但下次新会话又忘了。项目级规则文件的作用就是把这类高频纠正固化成长期记忆。

5. 代码评审闭环:让 agent 自己先瘦身再交给你的自审清单

Prompt和项目规则解决了“生成前”的问题,但AI生成的代码依然可能偶尔不受控。这时候不能只靠人去做代码评审,更划算的做法是在生成阶段就要求Agent带着一份“瘦身检查表”自查。这一段提供一套能直接用的自审清单和对应Prompt。

5.1 让Agent在交付前先做一轮“复杂度审计”

交付前自审和“直接生成”有着肉眼可见的差异。经我要求后,不少Agent在处理同一需求时会主动报告:“本实现未添加接口层;我原本想用工厂模式处理配置来源,但当前只有单一来源,所以改为直接调用;额外参数已移除。”

把这个流程固化下来的Prompt如下:

在你输出最终代码前,先执行一轮复杂度审计,并且把审计结论用几句话写在最前面。 审计清单: 1. 这个实现里每个interface/abstract class/factory是否都有超过一个真实调用方? 2. 有没有参数或配置项只是为“将来某个功能”留的? 3. 如果一个类只有一个使用点,能否直接合并到调用方? 4. 有没有引入了标准库或项目内已有工具已能实现的重复轮子? 5. 能否让一个只看了函数名的同事正确猜出它的行为? 只有当你对上述每一条都给出明确答案后,再输出最终版本的代码。如果审计中发现冗余结构,直接在输出版本里删除,并用一行说明删了什么、为什么能删。

这类Prompt之所以高价值,是因为它把“让模型自己批判自己”变成显式动作。模型本身完全有能力识别出“这个Factory不必要”,只是缺乏一个触发它去做批判的指令。你不让它查,它就默认一切设计合理;你让它查,它的诊断能力其实比多数人想象中好。

5.2 让Agent“证明”每个抽象的必要性

还有一个技巧是我最近才总结出来的:当模型的代码中出现一个新抽象时,要求它给出保留的依据。

比如,它给一段普通数据处理逻辑增加了一个Processor接口,而目前只有一个CsvProcessor实现。评审时你可以直接问:

“这个接口目前只有一个实现,请你说明接口层在当前代码里带来了什么可测试性或扩展性收益。如果拿不到净收益,把接口删掉,把CsvProcessor的方法变成模块级函数。”

大多数情况下模型沉默一秒后会列出文件名然后动手改。因为它真的算不出“单实现接口”在当前场景里有什么净收益。这个方式的本质是:把代码设计的证明义务交还给生成方,让它为每一层抽象负责。一旦形成这个规矩,它在生成阶段就会提前预判你后续会不会这样问,从而主动减少无必要的抽象。

5.3 用量化信号辅助发现复杂化

最终的人工评审环节,我建议可以盯几个相对客观的信号。抽象层数量、单文件行数、函数参数个数、循环嵌套层数、未被调用的公共方法数,这些数据不一定代表坏代码,但一旦异常飙升,就值得停下来问一句“这个复杂是不是被提前发明出来的”。

结合我日常review的经验,下面几条可以作为触发雷达的阈值:

  • 一个功能需求提交的代码量超过手写量的两倍以上,概率上是加了没用的结构。
  • 新增文件之间互相依赖超过两层,大概率可以合并或删层。
  • 一个配置项查遍整个代码库没有任何读取点,说明agent在做“无忧配置”。

AI辅助编程时代,人的核心角色正在从“写字的人”变成“定标准和做裁决的人”。代码是agent写的,但每一条“这里不需要抽象”的判断都来自你。

写在最后的一点体会

做了半年AI辅助开发,我自己最大的感触是:想让agent写出“朴实”的代码,光靠你告诉它“要朴实”是不够的。你需要把“朴实”翻译成它可以执行、可以自查、可以被你的review验证的一组规则,并且让这些规则出现在项目配置、Prompt约束和代码评审这三个环节里。

我现在的习惯是:任何一个和我AI协作者共享的仓库,根目录第一份文档一定是工程约束而不是README,因为每一次新会话的agent都会先读到它。这个措施比我说一百遍“别写复杂了”都管用。而当你明确告诉我自己只是要一个能用的模块、一个跑通原型的工具、一段将来会丢掉的数据处理脚本时,绝大多数agent其实都能给出超出预期的收敛输出。

真正难的不是让模型“会写简单代码”,而是让它在每个节点都记得“你不需要那么复杂”。这件事的权重,现在基本全在写需求的人手里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 11:14:39

从大理理想邦到数字场景:建筑美学与三维技术的跨界实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 11:13:06

STM32纯软件DDS信号发生器硬件实现方案

简介:本资源是一套基于STM32F103C6的DDS(直接数字频率合成)信号发生器完整嵌入式开发项目,面向嵌入式初学者、电子类课程设计学生及STM32实践开发者,解决从原理理解到软硬件协同实现波形生成的核心问题。项目涵盖正弦波…

作者头像 李华
网站建设 2026/9/5 11:12:08

纯C语言命令行表白程序:无依赖、无毒、可教学的浪漫代码

简介:这是一份面向编程初学者与浪漫程序员的创意实践资源,用C/C实现可视化表白程序,将基础语法与情感表达结合,解决技术学习趣味性不足、项目实践缺乏生活场景的问题。压缩包为4KB的ZIP文件,内含1个可直接编译运行的CP…

作者头像 李华
网站建设 2026/9/5 11:10:11

粒子群算法在机器人路径规划中的工程实践

简介:本资源是一套基于粒子群算法(PSO)实现机器人栅格地图路径规划的MATLAB完整实现方案,面向智能控制、机器人学及优化算法方向的本科生与研究生,解决已知静态环境中从起点到终点的避障最优路径搜索问题。压缩包共71个…

作者头像 李华
网站建设 2026/9/5 11:10:01

纯前端年会抽奖系统开发指南:从LocalStorage到动画实现

简介:这是一份开箱即用的企业年会抽奖网页源码,面向行政人员、活动策划者及前端初学者,解决无服务器环境下快速部署趣味抽奖环节的实际需求。资源包共24个文件,含1个核心HTML入口页、2个关键JS文件(其中member.js集中管…

作者头像 李华