最近技术社区里讨论 AI slop 的声音明显变多。Matt Pocock 先开了这个话题Dex Horthy 在回应时给了一个特别直接的观点减少 AI slop 的办法是写更少的代码。第一次看到这句话会觉得像口号真放到工程流程里才发现它几乎把问题重新定义了。AI slop 不是指代码报错而是指大模型生成出来的那种看起来很完整、实际上冗余、脆弱、难以维护的实现。它最麻烦的地方在于不报错能跑测试可能还全绿但接手的人会非常痛苦。与其费力用更长的提示词去纠正它不如先让代码库本身没有它发挥的空间。这篇文章不打算站队说“AI 代码都不行”而是把这条观点拆成可以执行的工程动作怎么识别 slop、为什么代码量下降后 slop 会减少、以及你在日常开发里怎么把这个原则落地。1. 先看清楚AI slop 在代码里是什么样子1.1 不是所有“能跑的代码”都是好代码很多开发者对 AI 生成代码的第一印象是“居然能跑”。这个判断标准太低了。AI slop 往往就是那种“能跑但很别扭”的产物。我给你一个很常见的例子。让人工智能读取一个本地配置文件然后把端口打印出来。任务本身只有一个动作但很多人拿到的结果是这种结构class ConfigLoader: def __init__(self, path: str) - None: self.path path self._data: dict[str, object] {} def load(self) - None: 加载配置文件到内存。 with open(self.path, r, encodingutf-8) as file: self._data json.load(file) def get_str(self, key: str) - str: 根据 key 返回字符串配置项。 value self._data.get(key) if value is None: raise ConfigError(fMissing config: {key}) return str(value) loader ConfigLoader(config.json) loader.load() port int(loader.get_str(port))如果你自己手写大概率是这样with open(config.json, r, encodingutf-8) as f: config json.load(f) port int(config[port])两段代码都能完成同一个任务。区别在于第一段里有一套类、两个方法、一个自定义异常这些东西在当下并没有为问题增加价值只增加了查看成本。这就是 AI slop 的典型形态不是做错了而是做得过多。1.2 识别 slop 的三个信号不同团队对代码风格的标准不一样但 AI slop 有几个跨团队都能对齐的信号。第一个信号是抽象数量明显超过需求。一个几十行的功能被拆成五六个类、接口、工厂、策略看起来结构清晰实际只有一个调用入口。第二个信号是出现了大量一次性辅助函数。这些函数理论上“以后可能复用”实际上除了当前这一个调用点之外没有任何引用。第三个信号是注释在解释“代码做了什么”而不是说明“为什么要这么做”。第一类注释是最没价值的。代码做了什么是编译器告诉你的事AI 只是把代码翻译成了人话。真正有价值的注释是记录约束和原因比如“这里不能走默认超时因为上游服务偶尔会慢”。我识别 slop 时还会看一个细节中间变量。AI 很喜欢把一个链式调用拆成七八个中间变量每一行都起一个新名字好像这样就能增加可读性。可当人review 的时候这些中间变量只是在增加阅读负担。2. 为什么“写更少的代码”能压住 AI slop2.1 生成成本已经很低评审成本还是你的AI 一天能生成几千行代码但你的评审速度没有变快一小时能认真看完的 diff 仍然是有限的。这是“代码量变少”最直接的理由生成是模型的后果是你的。当 AI 为一个只要 30 行就能解决的需求输出 200 行时你没有多得到 170 行的能力你多得到的是 170 行的评审义务。这些代码里还包括异常处理、参数校验、日志输出、防御性判断。每多一个分支就多一个测试盲区。少写代码本质上是在压缩未来必须由人接管的检查范围。所以不要在评审阶段只问“它能不能跑”要问“这段代码如果出问题我能在十分钟内定位到吗”。AI slop 恰恰是定位的噩梦因为它喜欢把问题分散到很多层里。2.2 模型会顺着你的代码风格“复读”很多人没意识到AI 输出的风格很大程度上不是由提示词决定的而是由上下文里的代码决定的。模型在补全时会模仿旁边文件的写法。如果你给它看的是一个已经堆满中间层、到处都是工厂和配置类的代码库它就会认为你也想要这种风格。反过来如果你的代码库干净、直接、每个函数只做一件事模型通常会延续这种简洁。Dex Horthy 说减少 AI slop 的办法是写更少的代码我理解就是在讲这个闭环代码库里的坏味道会喂养 AI让 AI 生成更多同样的坏味道。想从源头治理就得先把坏味道清出去。2.3 代码干净上下文里的噪声也更少现在的 AI 编程工具几乎都要往上下文里塞代码好让它理解项目结构。但上下文不是越长越好里面塞满了没用的历史文件、死代码、重复代码时模型反而会迷失重点。代码量少的项目有一个天然优势上下文干净模型拿到的信号质量高。它不需要在两千行的旧模块里猜测哪些代码还活着哪些已经废弃。这会直接影响生成质量。3. 落地第一步先给代码库本身做减法3.1 动手写提示词之前先删掉三样东西想让 AI 少写代码前提是你自己的代码已经足够“少”。我会定期在经常让 AI 修改的目录里做一次减法重点找三样东西死代码搜不到引用纯留着给心理安慰的函数和模块。重复代码同一个逻辑在项目里出现了三遍以上每个版本还略有不同。只服务单一调用点的抽象那个“未来可能用到”的通用方法通常永远不会有第二个调用者。清理的实践顺序不复杂。先用静态分析或编辑器的 Find References 找到无引用的函数再检查 import 列表最后跑一遍测试确认清理没有改变行为。很多 AI 辅助工具对重复代码特别敏感你要是不清掉它下次修改时会给每一处重复都加上新的注释和防御分支等于把问题复制一遍。3.2 能调库、能走标准库就不要让 AI 造轮子AI 格外喜欢生成自定义工具函数。任务是把字符串转成日期它能给你写一个专门的 parser任务是从 JSON 里读取配置它能给你写一个配置管理器。现实是绝大多数常用操作已经有了标准库或成熟依赖。写更少代码不代表手写得更短而是尽量不重复已经存在的功能。在让 AI 动手前我会先确认两件事这个项目已经有哪些依赖标准库里对应功能叫什么。然后在提示词里直接告诉 AI不要新增依赖不要自定义工具函数优先用现有库。只要这条约束写清楚输出长度会明显下降更重要的是代码的可维护性会上升。别人看到的是熟悉的 API而不是某个只有作者知道的私有封装。3.3 允许少量重复不要为了“优雅”提前抽象不少开发者把“写更少代码”误解成“更彻底的 DRY”于是要求 AI 把所有相似代码都合并成一个通用函数。这其实会制造另一种 slop为了追求不重复生成一个参数巨多、分支复杂、调用点反而更难读的抽象。判断标准可以定成“三次法则”。同一个结构出现两次没必要立刻抽象出现三次才开始考虑提取公共部分。如果 AI 在任务里主动提出“把这段逻辑抽成公共方法”而当前只有两个调用点时我会问它一个简单问题这个抽象在不同调用点之间的差异是通过参数堆出来的还是真的存在公共逻辑靠好几个布尔参数去控制不同行为表面上是少写了一个函数实际上是把复杂度转移到了调用方。这种代码不是减少是压缩压缩到最后反而更难维护。4. 让 AI 写代码时把“少”写进工作流4.1 任务拆小一次只让 AI 改一个点AI slop 出现概率最高的场景是你给它一个大而全的任务。“帮我写一个用户注册模块”这种需求几乎没有模型能给出干净的小 diff因为它必须自己决定边界、自己补全所有可能分支、自己组织目录结构。结果就是大段大段的生成代码堆在一起。我会把任务按变更粒度拆开。先建数据表再写接口再写业务处理每次只让 AI 专注一个点。单次生成的代码越少出错的面就越小评审时能覆盖的范围就越大。一个很实用的提示格式是先说现状再说要改什么最后说绝对不要碰什么。比如现有代码xxx.py 里有一个 load_user() 函数读取用户信息。 任务给 load_user() 增加一个 timeout 参数默认 5 秒。 约束 1. 不要改动其他函数。 2. 不要新增依赖。 3. 不要给现有代码追加注释。 4. 只输出改动函数的最小 diff。大部分模型会把这种任务当作“局部修改”而不是“重新创造”。输出质量和稳定性能有明显提升。4.2 不只告诉 AI “要做什么”还要告诉它“不要做什么”很多提示词都在描述期望结果却忘了划禁区。结果 AI 在完成任务时“顺手”帮你重命名了函数调整了变量名把两个文件的 import 顺序重新排了一遍。这些额外的改动会让 diff 变得特别大而且每一条都不是你要求的。在提示词里明确禁区是减少 slop 最便宜的方式。我常用的约束有这几条不要新增文件不要重命名已有符号不要修改无关函数不要重构已能工作的代码不要添加额外注释。看起来限制很多实际上是在保护旧逻辑不被 AI 的“审美偏好”污染。4.3 拒绝让 AI 做“顺手的重构”如果你常用 AI 编辑器就会发现AI 特别容易在完成任务时把相邻函数一起改掉。它的理由是“让代码更统一”。这种重构的可怕之处在于它经常是对的所以你会放松警惕。我现在的做法是只要看到 diff 里出现和本次需求无关的改动整个 diff 先不接收。无论那个改动看起来多合理都要单独确认。这不是不建议重构而是重构必须基于清晰的意图不能夹带在 AI 生成任务里混进来。否则你没法判断一次行为变化到底是哪行代码引起的。4.4 用 diff 评审代替“全量接受”AI 生成代码后真正决定质量的时刻是你按下接受键之前。我见过不少工程师窗口左边是 AI 生成的五百行新文件右边是旧代码他们只确认了接口能跑就直接全量替换。下一次出问题时根本不知道从哪看起。更稳妥的流程是要求 AI 输出小 diff然后只接受你真正看懂的改动。diff 越小后面的验证成本越低。如果这次生成明显比预期长不要硬着头皮评审先把任务再拆细一点或者补充之前的边界约束。把“diff 过大”当成一种需要回到上游去处理的信号而不是可以容忍的现状。5. 别把“少代码”搞成指标边界、误区和验收方式5.1 少不是目的可维护才是“写更少的代码”有价值但一旦把它当成 KPI就会产生新的问题。为了行数少把变量名压成一个字母为了少写几行把多个逻辑层层嵌套这种精简比 AI slop 更糟糕。写更少代码的真正含义应该是减少不必要的间接层、减少死代码、减少重复抽象、减少防御性冗余。它不等于禁止注释也不等于拒绝在某些场景下写更长更明确的逻辑。有些地方恰恰应该写长一点比如安全校验、权限判断、数据迁移脚本这些场景追求的是显式而不是优美。5.2 什么场景下要谨慎使用“少代码”这个原则如果你在维护一个大型遗留系统或者要处理严格的审计场景盲目追求代码精简会有风险。这种项目里一部分看起来“多余”的代码其实是历史约束留下的可能是兼容旧数据、可能是处理特定供应商行为。AI 不理解这些背景它只会根据“这段代码看起来没用”来做判断。所以我会把这个原则用在两处新的功能开发以及我们完全清楚上下文的老代码区域。对于不清楚来龙去脉的杂旧代码先不要鼓励 AI 做减法先让人工确认完再决定。5.3 一套可以自检的验收标准单看代码行数容易误判。我会用下面这张表来验收一次 AI 生成结果到底属不属于 slop检查项通过标准diff 规模新增代码是否超过了本次需求的合理范围抽象数量新类、新接口、新工具函数是否都有至少两个实际调用点改动范围是否只修改了需求相关的函数和文件注释价值删除注释后只看代码能否理解行为编译和测试修改后项目原有测试是否全部通过回滚成本出问题后能否快速回滚到改动前交接友好度不熟悉这个文件的同事能否直接看懂我的经验是AI 给出的结果只要有三项以上不满足就值得回到任务描述层面调整而不是在现有结果上继续打补丁。继续让它在同一段烂代码上补往往只会生成更多烂代码。5.4 当输出又开始发胖时的排查顺序就算约束都写好了AI 还是可能在某一次突然生成一大段冗余实现。此时不要急着调整模型按下面顺序排查。先看任务是否拆得足够小。如果提示词里包含两个以上的独立变更模型就有机会制造不必要的中转结构。再看上下文里是否有历史烂代码。模型会模仿相邻文件风格如果旁边就有一个三层的工厂类它会认为你也需要。然后看约束是否足够具体。“请简洁一点”这种说法几乎没有约束力要说清楚不新增文件、不加注释、不让现有符号改名。最后看输出中是否出现了和需求无关的改动如果出现直接把这次生成作废。判断一次 AI 输出是不是 slop先别看风格先问一个问题删掉那些“看起来高级”的抽象后功能是否保持完整如果答案是完全能保持那这些抽象大概率是在制造债务。我自己现在越来越倾向于一个观点AI 时代真正值钱的不是生成能力而是删减能力。能在正确的位置删掉不必要的东西比让模型多输出一百行“看起来专业”的代码重要得多。代码量少了AI 能犯错的面积也就小了。