如果你最近在用 AI 辅助写代码,大概率见过这样一个场面:需求还没想清楚,代码仓库里已经冒出一整层封装;每个文件都能跑,测试也过了,却没人解释为什么需要这个抽象。项目功能没有多多少,文件数量却开始膨胀。国外开发者讨论里给这类产出起过一个很直接的名字:AI slop。
最近关于 AI slop 的讨论里,有一个观点特别刺耳,但值得认真听:减少 AI slop 的办法,是写更少的代码。Dex Horthy 在回应 Matt Pocock 的类似观点时,也把方向指向了“以删除为中心的编程方式”。
这句话不是让大家不写代码。它的真正意思是:一段代码只有在“必须存在、能被解释、值得长期维护”的时候,才应该进入仓库。AI 辅助编程最大的风险,不是它写出了错误的代码,而是它让大量“无关紧要但看起来正常”的代码获得了进入仓库的资格。代码量越少,AI 产生的幻觉空间越小,人类需要解释、测试、审查和维护的面积也就越小。我认同这个判断,而且会从实操层面展开讲。
1. 先搞清楚:代码里的 AI slop,为什么比文本里的更难清理
1.1 同样叫 slop,文本和代码的成本完全不同
AI slop 最初更多被用来形容 AI 批量生成的文字、图片和视频内容。它们最大的特点是“量大、营养低、看多了会麻木”。文本 slop 放在信息流里,用户可以选择跳过;图片 slop 放在搜索结果里,用户能快速无感划过。
但代码 slop 没有这么好打发。一段进入代码仓库的代码,要被编译器解析,要被静态检查工具扫描,要被测试覆盖,要被同事阅读,甚至可能被部署到生产环境。它只要存在,就会成为后续修改的约束。代码不会因为你删掉了生成它的提示词就从仓库里消失,它会一直留在那里,等下一个维护者替它收拾残局。
所以代码语境里的 AI slop,不应该简单理解成“质量差的代码”,而应该理解成“语义密度很低的代码”。它能运行,但它没有清晰意图;它能通过测试,但它没有节省任何人的理解成本。
1.2 为什么语法正确、测试能过,仍然可能是负债
传统意义上的“烂代码”,通常还能靠代码评审和重构去修复。函数太长可以拆分,命名太差可以重命名,分支太乱可以整理。这些坏味道是有模式的,经验丰富的工程师一眼就能识别。
AI slop 更难处理的地方在于,它经常长着一副“合格代码”的样子。类名完整、函数分层清楚、注释也齐全,甚至错误码都提前定义好了几十个。可一旦你追问“这些错误码分别对应哪些真实场景”“这个抽象层到底屏蔽了什么变化”,答案往往是空白。代码的语法正确不能证明它有存在价值,测试通过也不能证明它和业务目标一致。测试只能说明“它按照当前的假设运行了”,不能说明“这个假设应该存在”。
更麻烦的是,这种代码没有传统的“负责人”感觉。人类写代码时,如果对某个模块不理解,至少知道要找谁问。AI 生成的代码则大量采用“看起来最标准”的方案,像一条自动装配线一样输出部件,没有人对那个部件产生过真正的所有权。于是,它进入了一种无人认领的状态:能跑,没人敢删,也没人愿意维护。
1.3 把代码量当成产出,是问题开始的地方
团队复盘时如果只看“新增代码行数”和“提交数量”,AI 辅助编程的产出会非常好看。但真实需求不会因为代码多就推进更快。更多时候,代码量增加意味着后续的 review 时间更长,回归测试范围更大,知识交接成本更高。
代码不是资产,至少不全是资产。它是需要长期偿还的债务:读它是成本,改它是成本,测试它是成本,删它也有风险成本。AI 让“创造代码”变得几乎免费,同时把成本转移到了所有未来接触这段代码的人身上。这就是为什么“写更少的代码”能成为一种抗 slop 策略。它不是保守,不是拒绝新技术,而是把注意力从“生产多少”转移到“需要保留多少”。
2. 写更少的代码,为什么能减少 AI slop
2.1 一段代码的隐藏成本,远不止编写那几分钟
如果只算写代码的时间,AI 生成 500 行肯定比人写 50 行快。但代码一旦进入仓库,它的生命周期才刚刚开始。每一次功能迭代,维护者都要先理解旧逻辑;每一次模块重构,阅读者都要区分哪些是必要逻辑,哪些只是装饰;每一次架构升级,所有人都要检查这段代码是否还能和新的约束兼容。
代码量不是加法,而是乘法。500 行代码的维护成本不是 50 行的 10 倍,可能是几十倍。因为行数越多,概念越多,概念之间的耦合也越多。AI 输出大量代码时,它不会自动替你消化这些耦合。你只是收到了一份需要用未来所有时间去阅读的长期账单。
“写更少代码”的第一个价值,就是减少别人需要被迫阅读和理解的面积。如果一个功能用 20 行就能表达清楚,就不应该让 AI 生成 200 行再嵌套几层抽象。删除那些没有提供新信息、没有表达新约束、没有隔离变化的部分,实际上是在给未来维护者减负。
2.2 大模型的输出范围越大,幻觉空间也越大
从模型行为的角度看,这个观点也成立。你让 AI 生成一个独立函数,它只需要判断输入、输出和局部逻辑,出错的点相对集中。你让 AI 生成一个完整模块,它要在几千行里同时维护状态、命名、依赖、异常、配置和边界条件,预测难度会迅速上升。
实际使用中很容易观察到一种现象:让 AI 单独写一个处理字符串的工具函数,效果通常不错;但让 AI“顺手把 controller、service、dao、model 都写出来”,它就会开始填充大量模板化内容。这些内容本身没有语法问题,可它会按照训练数据里的“常见示例结构”来猜测业务,于是产生大量不存在的接口、永远不会被调用的枚举、为将来准备的空方法。
大模型擅长局部生成,不擅长对全局进行长期一致性判断。上下文窗口在变大,不代表它在几千行代码里建立架构约束的能力同比例变强。把任务切小、把输出范围收紧,是减少 AI slop 最直接的手段。这和“上下文管理”很像:先给目录,再按需展开章节,而不是一开始就让 AI 把所有章节都填充完整。
2.3 写得更少,核心是收敛,不是把代码压成一行
这里要避免一个误解。“少写代码”不是鼓励用一行极其复杂的表达式实现所有逻辑,更不是拒绝在业务确实复杂时增加必要的代码。少,指的是删除那些没有回报的部分。
一个判断标准是:这段代码有没有真实调用者?它是否重复表达了一个已有概念?它是否为了未来根本不确定的变化而提前通用化?如果没有,那它就应该被删除。
我在实际项目里倾向于把“写少”翻译成“生成前收敛 + 生成后删除”。生成前,先确定任务边界;生成后,马上做一轮删除。删除不是盲删,而是在版本控制保护下,先确认调用链,然后让没有调用者的函数走完回归测试。这个动作看起来不像是在“写代码”,但它才是防止代码仓库被 AI slop 淹没的关键。
3. AI 编程里最常见的三类 slop,以及怎么识别它们
3.1 类型一:模糊需求下生成的“模块全家桶”
最常见的一种场景,是开发者直接对 AI 说“帮我实现一个用户评论模块”。AI 会非常热情地返回一个完整目录:Controller、Service、DAO、DTO、Entity、ExceptionEnum、Utility,甚至还有几个接口文档用的示例类。
问题在于,很多类并没有真实调用者。那些代码不是从业务需求里长出来的,而是从开源项目模板的统计分布里长出来的。比如一个错误码枚举里定义了 30 种异常,业务路径真正用到的不超过 4 种。以后如果要调整错误码协议,维护者要在这 30 个选项里逐一判断哪些该删、哪些能用、哪些只是 AI 为了让“错误码看起来完整”而补的。
识别方式很简单:生成完后,先不要看它写了多少,先去找每个文件有没有被其他代码引用。如果一个刚生成的文件没有任何真实调用者,就要警惕,它可能不是在解决需求,只是在表演“一个模块应该长这样”。
3.2 类型二:为一行需求封装的“概念层”
另一种 AI slop 更容易混过 code review。代码里本来只有两个函数,调用方觉得有点重复,于是让 AI“抽象一下”。AI 很快产出一个通用 Pipeline 类,把两个函数统一成一个入口,通过一个 options 参数控制所有分支。
两个调用点需要的最多是一个函数重载,但 AI 给出的是一整套“未来扩展框架”。真正的抽象应该源于持续出现的重复和清晰的变化方向,而不是为了现在看起来更通用。AI 之所以偏好这种写法,是因为它看过的大量开源代码库就是高度抽象的,那种风格在训练数据里太常见了。
识别方式是问一个问题:这个抽象真的消掉了一个重复点,还是只是把两个函数并进一个 switch?如果去掉新封装的类,直接保留原来的两个函数,代码会不会更容易读懂?很多时候,答案是“会”。这种抽象就不是在减少代码,而是在增加读者需要解码的概念层。
3.3 类型三:把教程式示例代码直接送进生产仓库
AI 的训练语料里有大量示例代码,它的目标通常是展示某个语法、某个库或者某个框架的用法。示例代码的第一使命是“让读者快速看懂”,而不是“承担真实业务约束”。所以 AI 生成的代码很容易带上示例风格:变量名偏通用,错误处理偏简单,输入校验偏弱,边界条件经常缺失。
比如你让 AI 写一个文件读取函数,它大概率会返回那种“教程版本”:直接 new 一个对象,没有处理文件不存在,没有处理权限问题,也没有释放资源细节。如果你只验证了 happy path,代码确实能跑,但进入生产环境后,所有异常路径都会变成问题。
开发者以前手动搜索“C 语言文件读写操作代码”的时候,至少还知道自己拿的是示例,需要改造后才能用。现在 AI 把这层提醒也去掉了,它会把示例包装成“针对你需求的完整答案”。最终进入仓库的,很可能是一个教程风格的半成品。
这里我建议用一个快速判断矩阵来识别代码:
| 形态 | 典型症状 | 需要问的问题 |
|---|---|---|
| 模块全家桶 | 新文件很多,但很多没有调用者 | 删掉这个文件,现有测试会不会变化? |
| 过度封装 | 两个函数被统一成复杂 Pipeline | 这个抽象消除的重复点,到底是什么? |
| 教程式代码 | 只处理正常路径,边界靠调用方自行兜底 | 这段代码在异常、失败、超载时会怎样? |
4. 真正有效的不是少写提示词,而是建立“收敛式生成”流程
既然 AI slop 的本质是“输出的保留成本远大于价值”,应对方式就不只是把提示词写短一点。比较有效的做法是建立一套收敛式生成流程:让 AI 先产生一个最小的可验证实现,然后立刻把多余的部分删除,最后只提交一个必要的最小改动。
这一套流程我一般拆成五步执行,适用于大多数在编辑器里使用 AI 辅助编程的场景。
- 写任务前,先用一句话说清“这段代码为什么必须存在”。说不清就不生成代码,先继续澄清需求。
- 裁剪输入上下文。不要把所有十几个打开的标签页都作为上下文喂给 AI,只把真正相关的函数和数据结构放进去。
- 限制输出形状。明确告诉 AI:不新建文件、不新增依赖、不加日志、不做过度封装,只修改某个具体函数。
- 生成后做一轮“删除评审”。让 AI 找出没有调用者的路径、无意义中间变量、为未来准备的参数,并删除。
- 提交前跑静态检查、单测和代码诊断,人再看一遍 diff。
这个流程的价值在于:它不允许 AI 直接成为“仓库内容决策者”。AI 负责生成候选代码,但你能不能活下去,仍然要经过删除和验证两道关卡。
4.1 在 prompt 里把“输出形状”定死
很多人低估了 prompt 里限定输出形状的作用。仅仅说“帮我实现一个功能”,AI 会自行决定结构。可一旦加上“不要新建文件,不要额外封装,只修改某个函数”,生成结果会收敛很多。
一个比较稳妥的示例结构是这样的:
不要新建文件,不要新增第三方依赖,不要添加注释和日志。 只修改 process_order 函数: - 当 status = refund 时,把 amount 置为 0; - 其他分支保持原样; - 不要为这个逻辑引入新函数。 先输出这份代码,再告诉我你删除了哪些分支。虽然不同工具对指令的遵循程度不一样,但把“输出边界”写清楚,会让模型很难继续生成一整套“全家桶”。如果你用代码方式生成示例,比如生成一段带结构的数据,也应该用最小的对象而不是完整的系统来验证。
4.2 把“优化重复代码删减脚本”这类需求拆成可诊断的小步
在真实开发里,很多团队会让 AI 做“优化代码”“重复代码删减”之类的任务。这类任务看起来很有价值,但对 AI 来说风险很高,因为“优化”意味着可以重排逻辑,“删减”意味着可以动结构。如果 AI 在理解不完整的情况下直接重写整个文件,通常会产生两个问题:要么破坏原有行为,要么为了让 diff 好看而强行压缩。
我的建议是先拆小步执行。第一步,只做重复代码检测;第二步,把重复片段抽成明确函数;第三步,确认所有调用位置都使用新函数;第四步,让 AI 再找一次是否还有重复概念。整个流程不是让 AI 一次性完成,而是每一个步骤都可以被 diff 审查。
这个过程里要用到代码诊断能力。很多编辑器都有问题面板、静态检查、圈复杂度提示、未使用代码提示。这些工具比 AI 自己的审美更稳定。如果 AI 生成的代码无法被编辑器正常索引,比如有人抱怨“VS Code 写 C 没有代码提示”,那往往不是工具坏了,而是生成的代码根本没有进入可分析的上下文中。对于工程代码来说,无法被索引就约等于无法被可靠维护。
4.3 一份针对 AI 生成代码的排查链路
如果你发现仓库里的代码开始变“滑”,不要先去改 prompt 风格,按下面的链路排查:
- 先看现象。是文件数量暴涨,还是单个文件越来越长?是代码能跑但没人敢改,还是开始出现“加一个新需求要绕开三个旧封装”的情况?
- 再看输入。最开始的任务描述是不是太模糊?是不是直接让 AI“实现一个完整模块”?有没有限定输入输出?
- 再看环境。生成时是不是把所有文件都拖进了上下文?AI 是否在全文件重写模式里运行?有没有引入多余依赖?
- 再看实现。这段代码有真实调用者吗?命名是否依赖“逻辑位置”而不是“业务意图”?异常处理和边界条件是否存在?
- 最后看验证。生成后有没有跑静态检查和单测?有没有在版本控制里保留纯删除的提交?如果只是“能编译就提交”,slop 基本上已经被放进仓库了。
这个排查链路不是为了证明某段代码很差,而是为了把问题还原到一个可修改的层面。只要问题定位在“输出太宽”,解决方式就不是换一个更强的模型,而是改变生成方式。
5. 什么时候该让 AI 多写一点,什么时候必须保持克制
5.1 先学会给任务分层
并不是所有代码生成都必须追求极端精简。有些场景让 AI 多写一点反而是合理的:
| 使用场景 | 是否适合多写 | 理由 |
|---|---|---|
| 一次性脚本或数据准备 | 适合 | 用完即弃,保留成本低 |
| 单元测试模板 | 适合 | 只要断言真实,多测几条数据没问题 |
| 正则表达式 / 数据转换 | 适合 | 逻辑相对封闭,便于快速验证 |
| 教学和语法演示 | 适合 | 目的是理解语法,不是进入长期生产 |
| 核心业务模块 | 不适合 | 需要长期维护,每行都要能被业务解释 |
| 跨服务调用流程 | 不适合 | 失败语义复杂,AI 容易把边界想得太简单 |
| 需要高度一致性的架构改动 | 不适合 | 模型缺乏对全局约束的长期判断 |
上面这张表只提供一个方向,不是绝对标准。真正判断时,仍然要看这段代码未来会不会被别人反复阅读和修改。
如果只是为了学习和验证某种算法,比如跑一个自然语言处理相关的小脚本、复现一个多模态模型代码,那让 AI 多生成一些完全没问题,只要跑完能保存结果就行。但这种代码不要顺手贴合到核心目录里。更好的做法是把实验代码隔离在一个独立目录,并写清楚执行方式,避免它某一天变成没人敢删的“参考实现”。
5.2 给每一段生成代码做一次“防 slop 体检”
我经常用五个问题来审查 AI 生成代码是否值得提交:
- 这段代码有真实调用者吗?如果删除后功能没有变化,那么它就是“存在但没有必要”的代码。
- 你能否用三句话解释它的业务意图?如果你依赖 AI 生成的注释才能解释,说明你自己还没理解它。
- 它是不是重复表达了一个已有概念?如果是重复,正确动作是先删除旧的再新建,而不是同时留两份。
- 如果要求 AI 只返回一个最小输入到输出的实现,行数能减少一半吗?能,说明之前的输出里混入了大量装饰性内容。
- 它是否沿用了项目里现有的异常处理、日志风格和类型约束?如果没有,即使逻辑正确,也需要重写才能符合项目上下文。
这五个问题不是为了让每个功能都缩到最短,而是要求每一段代码都承担明确的认知责任。AI 生成不是问题,问题是我们常常把它当成免检产品。代码评审要处理的不只是正确性,还有“代码是否真的需要存在”。
5.3 “写更少的代码”最终指向什么
回到开头那个观点。Dex Horthy 呼应 Matt Pocock 时强调的,不是“少写功能”,而是让开发者重新控制代码仓库的入口。AI 确实能写很多代码,但你不需要为它写的所有结果负责;你要负责的是那些最终被你保留下来的结果。
未来 AI 编程工具的进步方向,可能也不是无限制地生成更多代码,而是帮助开发者理解哪些代码可以被删除、哪些抽象可以被还原、哪些重复可以用更少的结构表达。真正能抵抗 AI slop 的,不是某个大模型的推理能力,而是人的删除能力和判断力:知道什么时候不看生成结果,知道什么时候对一段“能跑但没必要”的代码说不。
下一次使用 AI 辅助编程前,可以试着先写下这个问题:这段代码为什么必须存在?如果一句话写不出来,那就先别急着生成。等想清楚了,再用更少的代码把那个“必须存在”的部分实现出来。