“AI 写代码到底省不省时间?”这是最近我和几个前端团队交流时最常听到的问题。大家的感受出奇一致:代码量上去了,测试也能过,但 code review 越来越痛苦。打开一个 PR,先要在十几行“看起来很合理”的代码里,扒开重复的注释、绕圈的变量、伪防御式的 null 判断,才能找到真正在做事的那几行逻辑。这种感觉已经不再是“代码写得差”,而是代码里混入了一种特殊的坏味道——AI slop。
anti-slop 就是针对这个问题出现的项目。它把“AI 生成的低质量代码模式”当作 lint 的目标,专门为 Oxlint 定制一套有明确立场、有判断倾向的规则集。Oxlint 则是基于 Rust 实现的 JS/TS linter,以惊人的处理速度著称。这两者结合,传递的信号很明确:在 AI 编程助手成为标配的今天,我们需要的不是更温和的建议,而是更快、更严格、更有立场的代码质量闸门。
这篇文章会讲清楚三件事:AI slop 到底是什么,为什么反 AI 废代码这件事值得交给规则集来做,以及如何把 anti-slop 风格的规则接入自己的项目,并跑通“检查—发现问题—修复—验证”的完整闭环。如果你已经在用 AI 写业务代码,或者正在为团队引入 AI 编程工具而担心代码质量失控,这篇文章值得收藏。
1. 为什么现在需要 anti-slop 这样的规则集
1.1 传统 lint 解决不了 AI 时代的代码问题
过去我们用的 lint 规则,本质上是针对“人写代码的坏习惯”设计的。比如未使用的变量、隐式类型转换、缺少分号、魔法数字。这些规则有一个共同特点:它们检查的是代码是否违反规范。
但 AI 生成的代码问题恰恰不在这里。AI 代码的语法通常是对的,类型可能也对,测试也能跑通。问题出在代码的内容密度上——信息量很低,存在感很强。
举个例子,一个函数签名叫getUserInfo(userId),AI 在函数体第一行加上// 获取用户信息。这句注释对吗?对。有用吗?没有,它只是在复述代码本身。但传统 lint 不会报错,因为注释不是语法错误。类似的情况还有:
- 把返回值先赋给一个临时变量再 return,临时变量只用一次;
- 每个接口都套一层 try-catch,catch 里只打印日志然后返回 null;
- 一处逻辑被拆成五个函数,美其名曰“复用”,实际只有一处调用;
- 调用一个听起来很合理的 API,实际上这个函数根本不存在。
这类代码很难被“是否规范”的语言规则抓住,因为它们本质上不是规范问题,而是代码是否有存在意义的问题。
1.2 团队引入 AI 编程助手后的真实痛点
当一个团队开始广泛使用 AI 编程助手后,仓库的 PR 数量通常会上升,但每个 PR 的“信息密度”可能在下降。Reviewer 需要花费大量精力理解:
- 这段代码为什么这样写?
- 这个判断真的需要吗?
- 这个函数是业务必要,还是 AI 为了“更像一段完整代码”而生成的装饰?
最麻烦的是,这些问题很难通过“让 AI 更认真一点”来解决。AI 本身就倾向于生成平均数级别的代码,它会把网络上最常见的写法缝合进来,而网络上的最常见写法往往就是饱含各种防御和模板的保守写法。
anti-slop 这类规则集的价值就在这里:它把“代码品味”这个偏主观的东西,转变成可以自动执行的客观检查项。哪怕这个检查项不能覆盖所有问题,也至少能在 CI 阶段拦下一大批典型的 AI 废代码,减少人工 review 的负担。
2. AI slop 到底是什么
2.1 从概念到代码
“slop”这个词,原意是泼洒出来的糊状物,在网络语境里指那种“看起来像模像样、但没有真正营养”的生成内容。代码领域的 AI slop,就是语法正确、逻辑能跑、但充满冗余与无意义表达的代码。
它不是 bug,不会让程序崩溃;它也不是严格意义上的“坏味道”,因为很多资深工程师也会写出防御性代码。它的典型特征是可以被识别、被归类的模式。下面这六个特征基本覆盖了 AI slop 最常见的形态。
2.2 注释复述代码
这是 AI 生成代码最典型的模式。函数名写清楚了逻辑,注释又原封不动复述了一遍。
// 获取用户信息 const userInfo = getUserInfo(userId); // 遍历每一条订单 orders.forEach((order) => { // 处理订单 handleOrder(order); });这类注释不会让代码更容易理解,反而增加阅读噪声。真正有价值的注释应该解释“为什么这样写”,而不是“这段代码在做什么”。
2.3 多余的中间变量
AI 常见做法是把一个表达式的结果先赋给变量,再在没有改动的情况下使用这个变量。表面上看增强了可读性,但如果变量名只是参数名的变形,或者变量生命周期只有一行,这就是纯冗余。
const data = getUserInfo(userId); const userInfo = data; return userInfo;三行代码做了一件一行就能完成的事。
2.4 伪防御式编程
防御式编程本来是一种好习惯,但 AI 常常把它做成形式主义的样板。到处判断 null,却从不处理 null 的情况;try-catch 捕获异常后,只打一条日志,然后返回 null 或默认值。
function loadConfig() { try { const raw = readConfigFile(); if (!raw) return null; return JSON.parse(raw); } catch (error) { console.log(error); return null; } }这段代码的问题在于:catch 到错误后,没有降级方案,没有重新抛出,没有错误上下文,直接吞掉异常并返回 null。调用方拿到 null 之后同样不判断,最后错误在更远的地方以更隐蔽的方式炸开。
2.5 过度抽取函数与伪抽象
AI 喜欢制造“看起来可复用”的函数。如果一个函数全仓库只有一个调用点,并且函数内部只有一段业务逻辑,那这个函数到底是抽象还是包装?
function mapOrderList(orders: Order[]) { return orders.map((order) => order); }如果mapOrderList被四处使用,有变化需求,抽函数有意义。但如果它只在processOrders内部调用一次,且没有独立的业务语义,这就是一层无价值的壳。
2.6 模板化样板代码
在 API 层和业务服务层,AI 特别容易生成千篇一律的模板:统一的 try-catch、统一的日志、统一的返回结构。模板本身没有错,错在把模板复制到每一个函数里,却不根据业务调整异常处理和返回逻辑。
这类代码的特点不是某一行有罪,而是整段代码都在重复同样的模式,且信息量趋近于零。
3. 为什么偏偏选择 Oxlint
3.1 Oxlint 是什么
Oxlint 是 Oxc 生态中的 lint 工具,由 Rust 编写,目标是成为现代 JavaScript/TypeScript 工具链的高性能替代品。它兼容大量 ESLint 规则,可以直接在大多数项目里替换 ESLint 的检查环节。
从架构上看,Oxlint 与 ESLint 最大的区别在于实现语言和并行能力。Rust 原生性能加上多线程,让它在大型仓库上的处理速度非常可观。这不是说 ESLint 不好——ESLint 仍然是生态最成熟、插件最丰富的选择——但对于“规则数量越来越多、希望在本地和 CI 里都快速反馈”的场景,Oxlint 更合适。
3.2 anti-slop 与 Oxlint 气质匹配
anti-slop 这类规则集有一个天然诉求:规则可以很激进、很个人化、很多。因为“反 AI 废代码”不是把少数几个规则打开就完事,而是需要覆盖大量模式。如果跑一次 lint 需要几十秒甚至几分钟,开发者很快就会忽略检查结果。
Oxlint 解决的正是这个问题。它在规则数量增加的前提下,依然能保持较快的反馈速度。这对“让团队愿意在本地执行 lint”非常重要——工具再正确,如果慢到让人不想用,就等于零。
还有一个层面是理念的契合。Oxlint 的官方定位里,非常强调“默认推荐规则、减少配置负担、让检查结果可读”。anti-slop 则强调“有立场的规则集”。两者都反对那种“配置 500 条规则但全是摆设”的工程文化,更倾向于让规则真正生效。
3.3 也要清醒认识局限
Oxlint 目前还不能 100% 平替 ESLint 的完整生态。如果你的项目深度依赖某个人气 ESLint 插件,或者你有很多私有自定义规则,迁移前必须做一次规则覆盖评估。更稳妥的策略是:在传统 ESLint 流程保持不变的同时,把 Oxlint 作为辅助检查接入 pre-commit 或 CI,等规则覆盖确认后,再逐步切换。
| 对比维度 | ESLint | Oxlint |
|---|---|---|
| 实现语言 | JavaScript | Rust |
| 处理速度 | 规则增多后明显变慢 | 并行处理,速度优势明显 |
| 规则生态 | 最成熟,插件丰富 | 兼容大量 ESLint 规则,生态仍在成长 |
| 默认立场 | 只启用极少数规则 | 默认推荐规则更严格 |
| 与 anti-slop 结合 | 可用,但反馈耗时更长 | 更推荐,适合高频执行 |
4. anti-slop 规则集的核心规则类型
需要先说明:不同同类项目的规则命名可能不同,这里整理的是 anti-slop 这类“反 AI 废代码”规则集通常会覆盖的模式类别。接入项目时,具体规则名以项目 README 和配置文件为准。
4.1 无意义注释与误导性注释
这类规则检查注释是否为代码的简单复述,以及注释是否与代码行为不一致。后者尤其危险,很多 AI 生成的代码会先写注释,再按注释生成代码,但生成过程中逻辑已经变化,注释还停在原地。
触发场景:
// 把 id 转为字符串 return String(userId);“把 id 转为字符串”是String(userId)的逐字翻译,没有增加任何信息。如果团队要求每个函数都带注释,这种注释就会大量产生。规则的目的不是禁止注释,而是禁止“假注释”。
4.2 无意义别名与冗余变量
检查变量是否在赋值后从未被修改且仅被使用一次,并且这个别名没有起到澄清语义的作用。
触发场景:
const result = fetchData(); return result;这里的result没有比fetchData()提供更多信息,直接return fetchData()更清晰。规则会把这类别名标记出来,提示开发者判断别名是否真的帮读者建立了新的语义层次。
4.3 空 catch 与吞异常
检查 catch 块是否为空,或者是否只包含一行日志而后没有任何降级、重新抛出、包装错误行为。吞异常是 AI 生成代码里最恶劣的模式之一,因为它会让错误在系统里隐性传播。
触发场景:
try { await syncData(); } catch (error) { console.error(error); }如果这里没有降级逻辑,那么 catch 的唯一作用是“让程序不要崩”,但数据同步失败的业务影响完全被隐藏了。这类规则通常会建议:要么把异常包装后重新抛出,要么在 catch 里实现明确降级,要么至少给调用方一个失败信号。
4.4 只使用一次的伪抽象
检查只在一个位置被调用的函数,是否真的需要作为独立函数存在。规则不会直接判定它必须被内联,但会提醒开发者:这个函数的抽象成本是否得到了复用收益。
触发场景:
function getDisplayName(user: User) { return `${user.lastName} ${user.firstName}`; } // 全仓库只有这一处调用 export function renderUser(user: User) { return `<p>${getDisplayName(user)}</p>`; }如果getDisplayName确实只在这里使用,且没有独立变化的需求,更简洁的做法是直接内联。AI 特别容易生成这种“函数套函数”的结构来显得代码组织良好,实际上是在增加跳转成本。
4.5 冗余的模板样板
检查重复出现的 try-catch-return 结构、重复的日志格式、冗余的 DTO 转换等。这类规则的实现通常需要模式匹配,而不是单条语句。
触发场景:多个接口方法几乎共享同一个模板,只有中间一行不同。规则会提示把公共部分抽成真正的工具函数,而不是让模板代码铺满每一个函数体。
4.6 注释掉的代码
AI 在修改代码时,有时会保留旧的实现片段,用注释包裹起来“以防万一”。这类代码既影响阅读,又会误导后来者。规则直接检查连续多行被注释的代码块,识别出这不是说明性注释,而是残留代码,建议直接删除。
5. 环境准备与快速接入
5.1 安装 Oxlint
在开始之前,确保项目有 Node.js 环境,并且已经初始化过 package.json。推荐使用 npm、yarn 或 pnpm 任一包管理器。
npm install -D oxlintyarn add -D oxlintpnpm add -D oxlint安装完成后,可以查看版本确认安装成功:
npx oxlint --version如果看到版本号输出,说明 oxlint 已经可用。这一步值得先跑通,再往下配置 anti-slop 规则。
5.2 创建配置文件
Oxlint 会自动读取.oxlintrc.json。如果你使用的是包管理器托管的配置文件,也可以把它放在 package.json 的oxlint字段下。下面以独立的.oxlintrc.json为例,展示如何引入一个“反 AI 废代码”风格的规则集。
{ "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["anti-slop"], "rules": { "anti-slop/no-misleading-comments": "error", "anti-slop/no-useless-aliasing": "warn", "anti-slop/no-swallowed-errors": "error", "anti-slop/no-fake-abstraction": "warn", "anti-slop/no-commented-out-code": "error" } }这里用到的规则名,是为了说明配置结构而写的演示名称。真实接入时,应该以 anti-slop 项目实际发布的规则清单和插件名为准。配置思路是一致的:plugins声明规则来源,rules打开具体规则,并设置错误级别。
5.3 第一次运行
先对单个文件跑一遍,快速验证规则是否生效:
npx oxlint src/services/user.ts如果你希望检查整个项目:
npx oxlintOxlint 会输出检查到的错误和警告数量。第一次运行时,如果项目里已经存在大量 AI 生成的代码,你很可能看到成片的警告。这是好事,说明规则集正在工作。
6. 完整示例:用 anti-slop 风格规则抓出 AI 坏代码
6.1 一份典型的带“AI 味”代码
下面这个src/services/user.ts文件,集中了前面提到的好几种 AI slop 模式。把它放进项目,再运行 anti-slop 规则,就能直观感受检查过程。
// 文件路径:src/services/user.ts import { getUserInfo, getUserOrders } from '../api/user'; // 获取用户信息 function fetchUserInfo(userId: number) { const data = getUserInfo(userId); const userInfo = data; return userInfo; } // 处理用户订单 function processUserOrders(userId: number) { try { const orders = getUserOrders(userId); return orders.map((order) => { // 遍历每一条订单 return order; }); } catch (error) { console.log(error); return null; } }这段代码从语法上看没有任何问题,TypeScript 类型也正确。但读起来很累:两个函数名已经说清了意图,注释还在复述;data到userInfo的赋值完全是多余的;map没有做任何变换;catch 块吞掉异常后返回 null,调用方根本不知道发生了什么。
6.2 运行检查
npx oxlint src/services/user.ts预期输出类似:
src/services/user.ts 1:1 warning Comment only repeats code anti-slop/no-misleading-comments 4:3 warning Useless alias "data" anti-slop/no-useless-aliasing 7:1 warning Comment only repeats code anti-slop/no-misleading-comments 10:5 error Swallowed error with no fallback anti-slop/no-swallowed-errors 12:7 warning Useless map callback anti-slop/no-useless-callback 2 errors, 4 warnings输出格式因实际规则实现略有不同,但重要的不是格式,而是每一条提示都能指向一个真实问题。这个文件确实存在两种不同性质的瑕疵:一类是纯粹的信息冗余,另一类是错误处理逻辑的缺陷。
6.3 修复代码
修复的目标不是“消灭所有报错”,而是让代码更简洁、更真实地表达业务意图。
// 文件路径:src/services/user.ts import { getUserInfo, getUserOrders } from '../api/user'; export function fetchUserInfo(userId: number) { return getUserInfo(userId); } export function processUserOrders(userId: number) { const orders = getUserOrders(userId); return orders; }改动说明:
- 删掉复述代码的注释,因为函数名已经足够表达意图;
- 直接返回
getUserInfo(userId),去掉冗余中间变量; - 删除
map的空操作,直接返回orders; - 把 try-catch 整个去掉。原来的 catch 块既没有降级方案,也没有重新抛错,只是一行
console.log加一个null,这比不捕获更危险。如果调用方需要捕获异常,应该在更合适的位置做真正的错误处理,而不是在每个函数内部悄悄吞掉。
6.4 修复后验证
再次运行:
npx oxlint src/services/user.ts预期输出:
src/services/user.ts 0 errors, 0 warnings代码行数从 22 行降到 10 行左右,信息密度明显提升。这个例子虽然简单,但它说明了 anti-slop 规则集的核心工作方式:不是跟你讨论风格,而是直接指出哪些代码没有存在意义。
7. 运行结果与效果验证
7.1 判断成功的标准
npx oxlint的退出码为 0 时,说明没有 error 级别的问题。CI 中通常把这条命令作为检查步骤,如果退出码非 0,则构建失败。Oxlint 默认会输出错误和警告数量,一眼就能看出结果。
实际项目中,“全项目 0 error”不一定是最初就要达成的目标。尤其是存量代码库,第一次接入时先保证没有 error、warning 逐步下降,是更现实的节奏。
7.2 接入 pre-commit 钩子
为了让规则在本地提交阶段就生效,可以使用 lint-staged 配合 husky。当开发者提交代码时,只对被修改的文件执行检查,避免全量检查拖慢日常开发。
npm install -D husky lint-staged{ "lint-staged": { "*.{js,ts,tsx}": ["npx oxlint"] } }npx husky init在生成的.husky/pre-commit文件中加入:
npx lint-staged这个配置的含义是:只有暂存区里新增或修改的 JS/TS 文件会被 Oxlint 检查。CI 里仍然可以保留全量检查,形成“本地快速拦截 + CI 兜底”的双闸门。
7.3 接入 CI
以 GitHub Actions 为例,在.github/workflows/lint.yml中加入:
name: lint on: push: pull_request: jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx oxlint如果这一步在 CI 中失败,第一步要看的不是具体某条规则,而是失败时的退出码和第一条 error 输出。不要把整个 CI 日志从头翻到尾,先定位第一个 error,往往就是导致失败的原因。
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| oxlint 命令找不到 | 未安装依赖或 Node 版本过低 | 运行npx oxlint --version查看输出 | 重新安装oxlint,检查 Node 版本 |
| anti-slop 插件不生效 | 插件名或规则名写错 | 查看启动日志,确认插件加载情况 | 对照项目 README 检查配置名和规则名 |
| 现有代码库 suddenly 大量报错 | 存量代码本身包含大量 AI slop 模式 | 统计 error 与 warning 数量 | 先只开 warning,再逐步升级为 error |
| AI 助手改完后又引入同样的坏代码 | 提示词没有把规则作为硬性约束 | 查看 git diff 确认新增代码模式 | 把规则清单写入团队提示词模板 |
| 与 ESLint 报错重复 | 项目同时存在两套 lint 流程 | 对比两份检查报告 | 迁移期双跑,稳定后移除 ESLint 流程 |
| 某个规则太激进误报很多 | 规则默认严格度与团队阶段不匹配 | 查看具体误报案例 | 按目录关闭该规则或降级为 warning |
有一个排错原则值得记住:遇到 lint 问题时,先确认配置文件被正确加载,再谈规则本身。很多“规则没生效”的情况,其实是配置文件路径不对,或者插件名写在plugins里但安装的包名不一致。
对于 AI 反复引入坏代码的问题,比较有效的做法是把检查结果转成团队提示词的一部分。你可以对 AI 助手说:“请遵循项目中的 lint 规则,不要在代码里添加与代码行为重复的注释,不要吞掉异常,不要生成只使用一次的抽象函数。”这虽然不能保证百分百生效,但比什么约束都不给强得多。
9. 最佳实践与工程建议
9.1 分级启动,不要一次性全量整改
anti-slop 这类规则集的默认立场往往是“严格”的。如果直接把所有规则设为 error,现有代码库可能瞬间出现几百个报错,团队只能选择关闭规则或大改代码,这两种极端都不是好结果。更合理的做法是:
- 第一期:把规则设为 warning,只收集数据,不阻断提交。
- 第二期:只对新文件和新代码执行 error 级别检查。
- 第三期:逐步把存量代码纳入检查范围。
这个过程会让团队有心理预期,而不是把 lint 接入当成一次“代码大清洗”。
9.2 把规则写进 AI 工作流
如果你所在团队已经把 AI 编程助手当作基础设施,lint 规则就不能仅仅停留在 CI 阶段。把规则的核心要求写进团队的 AI 提示词,让 AI 在生成代码时就有“不要制造 slop”的意识,效果会好很多。
可以制作一个标准提示词片段,例如:
项目规范: 1. 不要添加与代码行为重复的注释。 2. 不要创建只使用一次的中间变量。 3. 不要吞掉异常,catch 中必须包含降级或重新抛出。 4. 不要生成仅有一个调用点的伪抽象函数。 5. 不要调用不存在或不明确的 API。AI 生成代码后,再通过 Oxlint 校验,形成一个“提示词预防 + lint 检测兜底”的组合。
9.3 规则要可解释,不要用工具压人
lint 规则最容易引起的冲突是团队内部的“规则争议”。很多人看到一条奇怪规则的第一反应是“这条没道理”。这很正常,因为“代码是否有必要存在”本身就是一道价值判断题。
处理这类分歧,建议用具体代码示例沟通,而不是只给对方看规则文档。拿出修复前和修复后的代码,对比阅读成本,往往比争论“AI 不该这么写”更有说服力。规则集的目的是降低团队整体理解成本,而不是制造新的教条。
9.4 关注高价值规则,而不是规则数量
anti-slop 的核心价值不在“规则多”,而在“规则准”。一个团队最值得优先落地的规则,通常集中在吞异常、重复注释、伪抽象这三类,因为它们直接决定代码是否好维护。至于某些风格层面的规则,优先级可以放低。
接入后,建议每两周复盘一次规则报告,看看哪条规则高频触发、哪条规则一直没人踩。没人踩的规则不一定没用,但高频触发的规则一定值得关注,它说明团队确实存在某种系统性的编码习惯。
10. 总结与后续实践路径
anti-slop 真正解决的问题,不是“AI 生成的代码是错的”,而是“AI 生成的代码常常没有存在的意义”。它把原来只能靠 reviewer 口头提的“这代码太啰嗦了”“这注释等于没说”“这个异常吞得没道理”,变成了一套可以被自动执行的规则。Oxlint 则提供了足够快的执行底座,让这套严格规则不至于拖累日常开发和 CI 流程。
如果你准备尝试,建议从一个小模块开始,而不是直接接入整个仓库。选一个最近由 AI 助手生成的业务文件,安装 Oxlint,按规则集跑一遍,把报错逐条修复。这个过程中,你大概率会重新认识自己项目的代码质量基线。
下一步值得继续深入的方向包括:Oxlint 与 ESLint 的规则迁移对照、团队 AI 提示词与 lint 规则的联动设计、以及如何基于报告数据制定更长期的代码质量改进计划。工具只是入口,真正有价值的是把“可维护性”从一句口号变成团队每天都能感知到的约束。