先说个最近让我有点睡不踏实的现象。上周帮朋友做一次代码评审一条 PR 摆在我面前1600 行12 个 commit其中 10 个 commit 的描述都写着 “fix: adjust AI generated code”。代码本身不能说烂注释齐全防御性判断一个不少可我就是讲不清楚这个改动到底想解决什么问题。更让我后背发凉的是我发现自己已经很难回答“这段代码为什么长这样”——因为它是 Cursor 这类 AI 编程工具顺手生成的写它的人自己也说不清。这个体验不是个例我周围越来越多团队靠 Cursor 提效半年后再打开代码库重复代码翻倍、依赖混乱、旧的兼容逻辑没人敢删。今天这篇文章我想认真拆一下AI 编程越来越猛代码库为什么反而越来越烂以及我们真正需要的那个“反 Cursor”工具到底是什么、怎么搭。1. 先别骂 AI 编程先看它到底给代码库签了什么“隐性账单”1.1 平均化输出你的代码库正在被“大多数人”接管大模型训练在几亿个 GitHub 仓库上它的本质任务是预测“下一行最像什么”而不是判断“这一行适不适合你的项目”。所以 Cursor 生成出来的代码几乎必然带一种强烈的“平均味”防御性 if 塞满、命名走最保守路线、异常处理模棱两可、注释把意图反复解释三遍。单独拎出来看每段都不差但合在一起你的代码库就正在被“GitHub 上的大多数人”慢慢接管。我习惯用装修来类比。你请了一个智能装修公司它手里只有海量户型图你告诉它要一个北欧风书房它确实能给你刷白墙、铺木地板、配一张原木书桌视觉效果在网上见过。但它不知道你家的书是 40 公斤重的大部头不知道你的采光下午三点就没了也不知道你需要在墙上留一排轨道插座。单看每个空间都能住住久了处处别扭。AI 写代码就是这样——它给出的不是“你的体系里最合适的实现”而是“全网出现频率最高的实现”。你的项目里原本那套统一错误码、统一日志规范、自定义的请求追踪链路在“平均化输出”面前会被一点点稀释。1.2 上下文窗口它记住你整个项目的方式一开始就是假的有人会说Cursor 不是能Codebase吗它能读整个仓库。确实检索比早期聊天式 AI 强太多但你要理解背后的机制它仍然是先把代码库切块、embedding 成向量、按相关性召回若干片段再把片段拼进有限的上下文里。这意味着它不是“懂了你的系统”而是“翻了很多页题库然后凭直觉作答”。召回质量受分块策略、关键词匹配、token 上限、甚至文件权限影响一旦关键约束没被召回它就会自信地发明一个不存在的接口或者重复封装一个已经存在的工具函数。我见过最典型的场景一个老项目里已经有src/utils/request.ts封装好了鉴权、重试和埋点结果让 Cursor 在新模块里写请求逻辑它直接又生成一个src/utils/http.ts看起来也是那么回事但完全游离在既有链路之外。不是它不努力而是它的上下文窗口里根本没有那条“全局唯一请求入口”的约束。你把这种代码合进主干等于默认允许“同一种概念以 N 种方式存在”这恰恰是大型代码库腐化的起点。1.3 它不背债务但债务会记在你头上还有一层更隐蔽的伤害。AI 不关心这个代码上线后谁维护、许可证有没有问题、有没有安全漏扫、三个月后这个依赖会不会被弃坑。它的目标函数是“让眼前这个对话任务看起来完成了”不是“让这个系统长期健康”。所以它天然倾向于局部最优给当前函数补一个补丁让编译通过哪怕这个补丁和两公里外另一个补丁逻辑重叠为当前需求新写一个工具函数哪怕仓库里有个参数差不多的旧函数正在被五个地方调用。更要命的是这些平均化代码一旦进入仓库就会成为“风格样本”。后续的 AI 召回它之后会以为“哦这个项目就是这么写代码的”于是继续产出更多同样风格的代码。企业咨询里有个词叫“组织性债务”放在代码库上也成立好的代码会带节奏平庸的代码同样会带节奏而且平庸的带节奏能力更强。你每一次允许 AI 生成的中庸代码直接进主干都是在给下一个 AI 投喂错误先例。2. Cursor 用是真的爽但爽完的代价往往都在半年后2.1 先说明一下我并不是来劝退 AI 编程的我自己重度用 Cursor最近还在拿它写一个内部运维工具纯效率角度我不可能退回到手写。它的舒适区非常明确把 Python 代码翻译成 Go 的骨架、写正则、生成单元测试的脚手架、快速理解一段陌生源码、给 CRUD 接口铺一个符合惯例的三层模板这些事它 30 秒完成的量我手写至少 20 分钟。所以问题的关键从来不是“要不要用 Cursor”而是“用了之后有没有同步调整你的工程防线”。2.2 高产的背面PR 变肥、提交变乱、评审失效AI 编程带来最直观的副作用是 diff 膨胀。人写代码通常是“一点点挤出来”的一次改动 10 到 50 行评审者能轻松看懂来龙去脉。AI 则倾向于一口气生成几百上千行因为它理解的“任务”是整个屏幕上的需求描述而不是一个增量步骤。评审 1000 行 diff 是什么体验大多数人直接放弃逐行看扫一眼绿条看有没有明显 bug然后合入。评审这个环节一旦形同虚设后面所有问题都会被放进主干。commit 历史也在变烂。Cursor 配合自动提交的功能会生成类似 “feat: implement login flow” 然后下一行 “fix: correct import path”整个分支看起来像一晚上的弹幕。不是不能看而是半年后你回溯某次事故时完全无法从这些提交信息里定位意图变化。代码评审讲究“可追溯”AI 时代这个底线被冲得很厉害。2.3 半年后的典型症状重复、死代码、命名漂移我自己的一个项目里引入 AI 编程辅助三个月后用jscpd扫了一遍代码库重复率从 3% 涨到了 11%。不是某个人偷懒而是 AI 在多个上下文里反复生成“看起来能用”的相似函数。它不知道另一个文件里已经有一个做了 90% 相同事情的函数。这还只是重复率另一类问题是死代码AI 发现上下文里找不到合适的调用方就顺手生成一个新接口旧接口没人删新接口没人用仓库里堆满了“理论上可用”的代码。更隐蔽的是命名漂移。同一个业务实体在 A 模块叫UserProfile在 B 模块叫UserInfo在 C 模块叫ProfileInfo每个模块单独看都自洽全仓库搜索时你会以为这是三个东西。错误处理也在漂移有的地方返回null有的地方抛异常有的地方吞掉异常打印个console.warn。这些都是 AI 逐个上下文“独立决策”的自然结果——它从来看不到自己最近五天写的代码有多分裂。3. 反 Cursor 工具不是反对 AI而是给 AI 招募一个“严格的上司”3.1 先回答一个关键问题“反 Cursor”到底反什么很多人一听“反 Cursor”以为是要退回命令行手写时代或者禁用 AI 编程。我的理解恰好相反。这里“反”字的意思更像“对偶”和“制衡”类似测试之于开发、code review 之于提交。CmdZ只能撤销单个文件撤销不了范式问题。我们需要的是另一类工具它的职责是“对 AI 生成的代码保持怀疑、执行统一约定、阻止无人认领的复杂度进入主干”。所以“反 Cursor”不是一个具体产品是一族策略类型检查、lint、架构约束、重复代码扫描、复杂度门禁、依赖审计、测试断言质量检查。这套策略的核心逻辑非常简单Cursor 负责高产出反 Cursor 工具负责高约束。一个拼命写一个拼命查两边形成张力代码库才能在“快速交付”和“长期可维护”之间稳住。3.2 五大工具族对应 AI 代码的五种病我目前工作里的防线主要分五个层面每层解决一类 AI 生成代码带来的问题防线层级常用工具检查重点治的是哪种 AI 病类型体系TypeScript strict、pyright、mypy空值、未定义接口、跨模块类型一致性模型臆想不存在的接口、返回类型含混代码风格与静态检查ESLint、oxlint、ruff 自定义规则复杂度上限、未使用变量、禁止静默 catch平均化防御代码、吞异常、死变量架构约束dependency-cruiser、eslint no-restricted-imports依赖方向、禁止业务层反向依赖基础设施层AI 无视已有分层随手跨层 import重复代码扫描jscpd、sonar相似代码块、复制粘贴率AI 独立决策造成的重复函数、重复封装变更与提交门禁commitlint、GitHub Action diff size 检查、hooks提交信息规范、单次变更规模、PR 文件数大 diff、弹幕式 commit、评审失效这套组合我称之为“自动 CR”它承接了原来需要资深工程师人工盯的一部分工作。注意这不是替代人而是先把那些“机械的、可规则化的约定”自动化让人力集中在意图判断上。3.3 为什么传统的 code review 已经不够了团队里有人会问“我们有 code review为什么还要上这么多工具”答案很现实AI 的产出量已经超过了人类评审的极限。一个人一小时最多认真评审 300 到 400 行 diff而 Cursor 一晚上能生成 3000 行。这时候把希望全部寄托在“让人更负责”上等于用木桶接瀑布接不住的。自动门禁的优势是它不疲劳、不吃情绪、不放过任何一条规则而且规则失败时会给出明确的出错信息。人类 reviewer 只需要回答“这个意图对不对、这个设计合适不合适”其余的交给规则系统这样反而能把人类精力聚焦到最有价值的问题上。4. 从 commit 到合并搭一套 AI 代码必须闯过的质量闸门4.1 第一步在提交前就给 AI 上“规矩”很多人忽略了一个比工具更前置的环节Prompt 约束。如果你在让 Cursor 干活之前不告诉它这个项目的禁忌和约定它就只能靠统计偏好自由发挥。我现在每个项目根目录都会放一份.cursorrules或AGENTS.md里面写清楚几条铁律只完成我描述的这一件事不要顺手重构无关代码不允许使用我未在提示词中列出的第三方依赖所有新封装的函数先搜索代码库里是否已有等价实现不允许静默捕获异常catch 后必须有日志或向上抛出修改后必须执行npm run typecheck和npm run lint。给 Cursor 的 Prompt 我也会刻意写“不要一次性生成整个功能只生成一个文件里的一小步”。这听起来很低效但实测下来小步生成 每步人工确认比一次性生成 800 行然后返工总耗时少得多。4.2 第二步提交与分支层面的闸门本地提交时用 lint-staged 配合 commitlint让不符合规范的提交根本进不了历史。我的lefthook.yml里大概是这样pre-commit: commands: lint-and-typecheck: run: npx lint-staged npm run typecheck commitlint: run: npx commitlint --edit $1分支层面我会在 CI 里加一个“PR 规模守卫”逻辑不复杂就是统计改动文件数和总行数超过阈值就直接在 GitHub 标红。因为 AI 很容易一晚上生成 300 个文件的改动这种 PR 无论是 review 还是回滚都是灾难。我设的阈值通常是文件数不超过 20 个总新增行数不超过 800 行不满足就让 AI 拆任务重做。你可以在 Action 里用一行判断git diff --name-only origin/main... | wc -l拿这个数字对比阈值超了就 fail。4.3 第三步静态检查与架构雷达静态检查这里除了常规规则集我更推荐针对自己项目“历史伤疤”加自定义规则。比如我参与的一个后台项目规定业务代码禁止直接import某个老旧的utils/fetch-old所有请求必须走新的统一封装这个用 ESLint 一行就能搞定// .eslintrc.cjs 片段 module.exports { rules: { complexity: [error, { max: 8 }], typescript-eslint/no-unused-vars: [error, { argsIgnorePattern: ^_ }], no-restricted-imports: [ error, { patterns: [src/utils/fetch-old] } ] } };架构层面更推荐dependency-cruiser它能把依赖方向画成可执行的规则。比如禁止domain层反向依赖infra层配置看起来类似{ forbidden: [ { name: no-domain-to-infra, from: { path: ^src/domain }, to: { path: ^src/infra } } ] }这里有个重点规则不能设得太细否则 AI 没法干活人也嫌烦。我一般只维护 5 到 8 条“红线”规则——比如不允许业务层直接拼 SQL、不允许 catch 后无日志、不允许绕过统一错误处理器。多了守不住少了没意义。把红线放进自动检查后AI 生成的代码一旦越界CI 会直接拒绝合并根本不用等人类 reviewer 来发现。4.4 第四步测试覆盖与“断言质量”AI 非常会写“假测试”这是我在项目里发现的最迷惑人的坑。它生成的测试通常会通过函数内部逻辑也能跑通但断言非常空洞。最典型的就是expect(response).toBeDefined();这种断言等于没测只能证明“代码没抛异常”不能证明“行为符合预期”。真正的断言应该锁住具体结果expect(response.user.id).toBe(u_123); expect(response.count).toBe(3);所以我的 CI 里不仅看覆盖率阈值还会加一层“断言密度”的意识重要业务模块的测试人工 review 时先看断言再读测试逻辑。Coverage 高但全是toBeDefined的测试在我这里等于没有。这一条建议写进团队完成定义里——DoD 里加一句“AI 新增的所有测试必须由提交者本人逐条检查断言是否锁定了真实行为。”4.5 第五步人机结合的 review 清单自动门禁过了不等于可以闭眼合入。针对 AI 代码我手上有一份专门的 review 清单和普通 CR 不完全一样首先查命名看同一个概念在改动范围内是否出现了多个叫法其次是错误处理所有 catch 都必须有归宿——日志、告警、向上抛三者至少占一个然后是死代码AI 经常生成没人调用的导出函数查一遍ts-prune或knip。最后还要看“边界条件”AI 最擅长处理正常路径最不擅长处理空数组、空字符串、超时和重试。这份清单不长但每次都有效。它解决的不是“这行写得对不对”而是“这段代码是否从骨子里融入了项目现有的体系”。5. 我实际踩过的几个坑以及现在的补救工作流5.1 十二个“数据请求函数”的事故有一次我让 Cursor 帮忙重构一个老页面的请求逻辑把散落的wx.request统一换成公司自定义的fetchWrapper。它确实很听话把 12 个页面都改了但问题也来了每个页面都生成了一份“看起来很合适”的请求函数有的加了重试参数有的加了超时配置有的把错误处理写进页面里。一通扫描之后发现我的警告名单是真实的——引擎从头写了一遍相同的 body复制出 12 个近似但又不完全相同的函数。当时项目里没有任何层级的 lint 规则禁止直接import旧请求库人工智能可以在每个页面上随意造一个“包装器”。现在补救的做法是先定唯一入口然后用no-restricted-imports锁死旧的wx.request再重新跑jscpd把重复率压回来。这个事故给我的教训是——让 AI 做大规模替换之前必须先建立“唯一出口”的自动约束指望它“自己发现”是不现实的。5.2 被吞掉的异常数据神秘消失另一个案例更吓人。一个异步加载用户配置的函数AI 给它加了一层try/catchcatch 里没有任何日志直接return null。线上数据神秘消失一整天用户反馈“配置看不到了”我们查监控日志一片空白因为异常被吞得干干净净。最后是错误追踪平台抓到一条 starvation才定位到这个被吞掉的异常。靠人查代码根本没戏这个文件上周刚被 AI 写过reviewer 只看了正常路径。从那之后我把“禁止静默 catch”写成了团队的红线规则。ESLint 本身没有特别顺手的禁止 catch 规则我们的做法是让自定义 AST 规则去扫描catch块如果块里既没有throw、没有日志调用、也没有错误处理函数直接报错。这条规则比任何代码规范文档都管用——因为机器不会忘。5.3 依赖升级事故它顺手把核心 SDK 升没了有次让 Cursor 修一个旧依赖的兼容问题它居然顺手把几个核心依赖更新到了 latest。等我反应过来构建已经挂了半小时。你没法完全怪 AI——它的直觉是“版本不对就升级”但升级这种操作在真实项目里牵扯许可证、API 变更、子依赖冲突是最需要人工决策的一类操作。现在的防护很简单package-lock.json和yarn.lock纳入保护CI 里如果检测到 lock 文件变更但用户没有在 commit message 里显式声明“dep:”就自动拦截。同时 prompt 里明确写“不要做主依赖升级”。这条规则执行之后这类事故再没发生过。5.4 一晚上 300 个文件的 PR最终以回退收场还有一次是让 AI 做跨多个模块的重构一觉醒来分支上 300 个文件变更5000 多行 diff。这种 PR 是没法 review 的没人能在一上午看完 5000 行并给出有效意见团队最后的选择是整体回退把重构拆成五轮每轮限定一个目录逐步推进。那次之后我彻底接受了一个观念AI 是放大器和加速器但你得给它装上“节流阀”否则它会把你带到一个无人能接管的地方。6. 把 AI 当结对 Junior把规则当 Senior——我的协作心态与日常 checklist6.1 心态转变从“让 AI 写”到“让 AI 改”如果你想用一句话总结我现在的方法论那就是人写意图AI 写实现规则写底线。这句话想了很多年才落地。早先我用 AI 的习惯是给它一个大的目标比如“实现订单模块”然后躺着等它写完。后来发现它写的东西我不能完全理解但看着能用这种“半懂状态”最危险。现在我的工作流回到最古典的模式——先自己把需求拆成小任务、定义好接口签名和边界条件甚至先写测试用例再让 AI 去填充实现最后通过自动门禁和人工 review 验收。它的定位从一个“外包团队”降级成“结对 Junior”而我重新回到“技术负责人”的角色。6.2 我现在的日常 workflow 长这样拆解下来的步骤并不复杂按顺序写出来给大家参考需求拆解把本次任务拆成 1 小时内能完成的小步骤每步只碰一个目录。人先定契约先写接口定义、类型、函数签名、预期的错误处理行为。AI 填充实现让 Cursor 只实现最内层的一个函数或一个组件提示词里附带仓库规范说明。自动闸门本机跑 lint typecheck 单测提交时跑 commitlintPR 时跑 diff size 守卫和 architecture 检查。代码质量体检jscpd扫重复、knip扫死代码、eslint扫复杂度。人工 review只做意图与设计的判断对照我上面那份 checklist不逐行读样板代码。小步合并每次合入都控制在两三个文件以内的规模合并频率提到一天多次。这套流程看起来笨但实际运行起来非常稳。Cursor 负责把机械劳动的成本打下来规则系统负责把机械错误的概率打下来人只需要处理真正需要判断力的那部分。6.3 我给团队的“代码库健康体检”清单如果你也想给现有代码库做一次“反 Cursor 体检”我建议按这个顺序跑一遍。先跑重复代码扫描看重复率是不是在一个季度内明显上升再跑依赖关系检查看有没有逆向依赖、跨层 import然后跑死代码检测统计无人调用的导出函数数量接着看 PR 历史统计最近一个月平均 PR 文件数和提交数如果单个 PR 平均超过 20 个文件就要警惕了。最后检查测试断言质量抽样读几个新写的测试看看是不是大量toBeDefined()空断言。这一套下来基本能判断你的代码库是否正被 AI 编程悄悄“平均化”。把话说直白一点工具不会毁掉代码库没人管的工具才会。Cursor 给了我们史无前例的产出速度但它不会为你的代码库后果负责会负责的只有你自己工程体系里的那些硬规则。“反 Cursor”式工具不是要回到手写时代而是把 AI 的疯狂输出纳入人类可控的轨道。以我个人经验规则越硬AI 反而越乖门禁越严格你才越敢放开手把那些重复劳动全部交给它。