
在 SlopCodeBench 论文里,有一个 117 行的函数把整份实现的分支判断几乎全堆在了一起——这是 code_search 题目最后一个检查点里的 find_matches_in_file()。论文给出的解释是,Agent 遇到新需求时,极度倾向于把新逻辑顺手补进已有函数。
这篇论文把需求分成几轮交给 Agent,每轮继承它自己上一轮的实现,用数字记录代码结构如何逐轮退化。比起泛泛争论“AI 代码是不是垃圾”,这种量化度量显然更有信息量。
但这类指标同样带来了一个新诱惑:能不能给仓库算出一个 Slop 质量分,按分数决定代码能不能合并?
论文分别衡量冗余代码的比例,以及复杂度集中在高复杂度函数里的程度。这些结构信号能指出值得检查的位置,但没有直接测量接手的人要花多久才能改好代码。
Slop 这个词现在承担了太多意思。有人用它指重复代码,有人指没必要的抽象,也有人只是嫌实现不符合自己的习惯。再加上一句“这是 AI 写的”,产出来源也混进了质量判断。
这些问题不该靠一个标签合并。重复逻辑可以查,函数里的分支可以数,某层抽象是否必要却要看它在当前项目里承担什么。来源本身解释不了这些取舍。
一段人工写的代码,也可能让每次小改动都要跨过十几个文件。Agent 写的实现,如果遵守现有边界、容易检查,也不能只凭来源就被判坏。
于是,把感觉变成指标很有吸引力。它至少能让大家对着同一批代码讨论,减少“我觉得不舒服”与“我觉得挺好”之间的空转。
Earendil 在 9 月的一篇文章里梳理了几种尝试,包括用新增行数、冗余和复杂度观察 Slop。作者也指出,新增行数有时是有效线索,但一旦直接以它为优化目标,意义就会变化。
改动突然长了很多,确实值得问原因。可从“新增行数可能暴露问题”跳到“新增行数越少越好”,会奖励另一种坏代码:把信息挤在一起,把原本明确的处理藏进难读的表达式。
同样,复杂度集中在大函数里,可能意味着很多新逻辑被不断贴进同一个地方。把函数拆开可以改变统计结果,却未必改变理解成本。
读一个入口需要不停跳到几个只调用一次的小函数,再把条件拼回来,代码依然不好读。局部数字变得漂亮,不保证读者少记了任何一件事。

也有拆分确实澄清了职责、新增代码确实补齐了必要行为的时候。指标分不出这次属于哪一种。
质量分适合帮助定位问题,不能单独决定一个改动是否值得合并。
把评分交给模型当评委,也绕不开同样的问题。模型仍然需要一套标准,才能解释它在判断什么。给出一个九分,只是把讨论结果换成了数字。
如果标准偏好短函数,它可能奖励机械拆分;如果偏好完整架构,它可能奖励眼下没有必要的层次。评分有理由、有术语,不等于标准适合这个项目。
更有用的回答应该指出具体负担:同一条规则在哪里重复实现,改一个条件会触碰哪些地方,哪些中间状态需要读者额外记住。这样的意见可以被核对,也可以被反驳。
至于这些负担后续有没有兑现,要放到下一次需求变化里观察。一次生成的代码,总是面对已经知道的需求;现在能工作,和继续工作需要多少代价,是两个时间点的问题。
SlopCodeBench 有价值的地方,在于让 Agent 接着维护自己的实现。下一轮需求到来时,保留上一轮工作区,但不保留对话上下文,也不用标准答案替换旧代码。早期选择造成的负担会留下来,不能通过每次重新开始被抹掉。
这也提醒我们,不必只盯着最后一次得分。一段代码初始指标很好,后来每次追加行为都需要大改,说明最初那个分数没有捕捉到相关风险。
当然,未来需求不可能被完整预测。不能因为某次新要求需要重构,就断定此前设计错误。为了一个永远不会发生的变化预先搭好所有抽象,本身也会增加维护负担。
因此,评估时更合理的是选择项目实际可能发生的变化,比较几种实现面对相同变化的表现。测试用于检查旧行为有没有被破坏;修改范围、重复逻辑和接手所需的解释,则补充说明实现有多难继续维护。

这些观察拼不成一张通用榜单。同一个函数在小工具里足够清楚,放到长期维护的服务里可能需要另一种组织方式。论文的指标只统计一部分结构特征,评测任务、模型和会话条件也与真实团队不同,不宜把某个分数直接搬进生产审批。
我会先把指标用于定位异常:代码为什么忽然长大,复杂度为什么集中,重复为什么增加。然后回到那次改动,判断新增的负担是否由需求要求,还是实现自己制造的。
审查一次清理改动时,我会沿着一条具体规则检查:原来要改几个地方,现在要改几个地方;原来需要记住哪些中间状态,现在还剩哪些。删了多少行、评分涨了多少,放在这些问题之后看。
文中链接
- 论文https://arxiv.org/html/2603.24755v1#S2.SS3
- 文章https://earendil.com/posts/measuring-code-sloppiness/