智能调试建议先过 ESLint 和 tsc 再采纳:一个交叉验证流程的实践记录

代码补全和调试建议里的低级错误,往往不是模型不够聪明,而是它缺少一个编译器和 linter 的即时反馈回路。把 ESLint 和 tsc 的结果喂给智能调试建议做交叉验证,本质上是在给模型补上“代码能跑”这个物理约束。

问题不在模型,在于反馈闭环的缺失

最近半年我在几个项目里密集使用 AI 调试助手——不是那种聊天式问答,而是直接在 IDE 里对报错代码右键“智能修复”的场景。一个反复出现的模式是:模型给出的修复建议,语法上看起来合理,逻辑上也能解释得通,但一跑 ESLint 就是一堆 warning,tsc 直接报类型不匹配。

举个具体的例子。一段处理用户输入的代码,原始报错是 Property 'trim' does not exist on type 'string | undefined'。智能助手给出的建议是这样的:

const username = input.trim().toLowerCase();

它直接把 input 当成了 string,完全忽略了 undefined 的可能性。正确的修复应该是:

const username = input?.trim().toLowerCase() ?? '';

或者用类型守卫先收窄。模型为什么犯这个错?因为它生成建议时,参考的是它见过的海量“正常代码”的模式,而不是你这个项目里 input 变量真实的类型定义。它没有运行 tsc,所以它不知道 inputstring | undefined

这就引出了核心判断:智能调试建议的质量上限,取决于你能给它提供多少“硬约束”。 ESLint 的规则配置和 tsc 的类型检查结果,恰恰是最硬的那类约束——它们不是风格偏好,是机器可以验证的客观事实。

交叉验证流程的具体做法

我现在的固定流程是这样的,分三步走,每步之间有明确的判断标准。

第一步:先让本地工具链跑一遍,拿到完整的错误清单。

在采纳任何 AI 建议之前,我先在终端跑:

npx eslint src/ --max-warnings=0
npx tsc --noEmit

注意 --max-warnings=0 这个参数。很多团队的项目里 ESLint 有几十个 warning 是常态,大家习惯了视而不见。但交叉验证场景下,warning 也必须清零,因为 AI 建议引入的新代码可能恰好触发了某条被忽略的规则。--noEmit 则保证 tsc 只做类型检查,不产生编译产物,速度更快。

记录下当前的错误列表。这个列表就是后续验证的基准线。

第二步:让 AI 建议“过筛”,而不是直接采纳。

拿到 AI 的修复建议后,不直接应用到源码。先把建议的代码片段粘贴到一个临时文件里,或者在脑子里快速过一遍这几个问题:

  • 这个建议是否改变了函数签名?如果改了,调用方是否同步更新?
  • 建议的代码里有没有引入新的依赖或类型断言?
  • 对于 React 项目,是否触碰了 hooks 的依赖数组?

然后,把建议应用到代码里,立刻重跑第一步的两条命令。对比新的错误列表和基准线的差异。如果新增了任何 ESLint 报错或 tsc 类型错误,这个建议就需要打回。

这里有个关键细节:新增错误比原有错误更有参考价值。 原有错误可能是历史遗留问题,但新增错误直接指向 AI 建议引入的缺陷。我遇到过好几次这样的情况:AI 建议修复了一个类型错误,但引入了两个新的 ESLint 违规,其中一个是 react-hooks/exhaustive-deps,这意味着它改动了副作用逻辑但没正确声明依赖。

第三步:把验证结果反向喂给 AI,形成第二轮对话。

如果 AI 建议被 ESLint 或 tsc 打回了,不要简单丢弃。把具体的报错信息复制回去,让模型基于这个反馈重新生成。比如:

你刚才的建议在 src/components/UserForm.tsx 第 42 行触发了 react-hooks/exhaustive-deps 警告,因为 fetchUser 函数在 useEffect 内部被调用但没有加入依赖数组。请重新给出修复方案,确保通过 ESLint 规则 react-hooks/exhaustive-deps 和 tsc 类型检查。

这种带精确错误信息的反馈,比“你的建议不对,再想想”有效得多。模型能根据具体的规则名称和行号调整策略。我统计过自己最近 20 次交叉验证的记录:第一轮建议通过率大约 40%,第二轮带反馈后通过率升到 75% 左右,剩下 25% 的情况模型反复在同一个坑里打转,这时人工介入反而更快。

这个流程真正在解决的,是“可验证性鸿沟”

如果只是说“AI 建议要谨慎采纳”,那是正确的废话。这个交叉验证流程的价值在于,它把“谨慎”从一种态度变成了一套可操作的机械步骤。

传统的代码 review 依赖人的经验和注意力,但人的注意力是稀缺资源,尤其在调试场景下,你本来就处于“代码有问题”的焦虑状态,更容易被一个看起来合理的建议带偏。ESLint 和 tsc 的价值在于它们是无情的、一致的、可重复的。它们不会因为建议看起来很有道理就放行,也不会因为建议来自知名模型就降低标准。

更进一步,这个流程还解决了另一个隐性成本:信任的建立速度。如果你每次采纳 AI 建议后都要手动跑一遍测试、手动检查类型,成本太高,最终会放弃使用 AI 助手。但把验证步骤前置并自动化之后,你可以更快地建立对某个模型或某个场景的信任——比如,你发现某个模型在你项目的 React 代码上通过率很高,但在 Node.js 脚本里经常翻车,那你就可以针对性地调整使用策略。

工具链层面的自动化空间

目前这个流程还是手动执行的,但自动化空间很大。几个可行的方向:

Git hooks 集成。pre-commit 阶段强制跑 ESLint 和 tsc,这样任何 AI 建议的代码在提交前都会被拦截。但这只是事后拦截,不是事前验证。

CI 里的“建议质量”追踪。 如果你在 PR 里使用 AI 生成代码,可以在 CI 里加一个步骤,对比 AI 建议前后的错误数量变化。如果 AI 建议减少了 3 个错误但新增了 5 个,这个建议的“净价值”就是负数。

IDE 层面的即时反馈。 这是最理想的形态。目前 VS Code 里的 Copilot 或 Cursor 已经能在建议旁边显示 ESLint 诊断信息,但 tsc 的类型错误反馈还不够实时。如果未来 IDE 能在 AI 建议生成的瞬间就叠加类型检查结果,那交叉验证的成本会趋近于零。

我目前的折中方案是:在 VS Code 里开启 typescript.tsserver.experimental.enableProjectDiagnostics,让 tsserver 更积极地推送项目级类型错误。同时把 ESLint 的 lintOnSavevalidate 配置调成最严格模式。这样 AI 建议一出现,旁边的红色波浪线就是第一道过滤。

一个反直觉的发现

经过几个月的实践,我发现这个交叉验证流程最大的收益,不是拦截了多少错误建议,而是它改变了我和 AI 助手的协作模式

以前我是“发起请求—接收建议—人工判断—采纳或否决”。现在变成了“发起请求—接收建议—机器验证—反馈结果—再次接收”。多出来的这一步机器验证,看似增加了流程长度,实际上减少了我做决策时的认知负担。我不需要每次都深入理解 AI 建议的每一个细节,我只需要看懂 ESLint 和 tsc 的报错信息就够了——而这些报错信息是我每天打交道、已经形成肌肉记忆的东西。

换句话说,交叉验证流程把“判断 AI 建议是否正确”这个困难问题,转化成了“判断 ESLint/tsc 报错是否可接受”这个简单问题。 而后者,一个写了五年以上 TypeScript 的工程师,几乎不需要思考就能完成。

这个转化,才是整个流程的核心价值。

常见问题

交叉验证能完全替代人工 review 吗?

不能。ESLint 和 tsc 只能验证语法、类型和部分代码风格规则,它们无法验证业务逻辑的正确性。AI 建议可能在类型上完全正确,但业务逻辑是错的——比如把 > 写成了 >=,tsc 不会报错,但结果是灾难性的。交叉验证解决的是“建议是否合法”,不是“建议是否正确”。后者仍然需要人对业务上下文的理解。

如果项目本身 ESLint 和 tsc 就没通过,这个流程还有意义吗?

有,但需要调整基准线。如果项目里已经有 200 个历史 ESLint 错误,那“新增错误”的判断标准就失效了,因为新增 5 个错误在 200 个的基数上很难被注意到。这种情况下,建议先花时间把存量错误清零,或者至少对 AI 建议涉及的目录建立局部零错误区域。否则交叉验证的过滤效果会被噪声淹没。

这个流程适合所有类型的 AI 调试建议吗?

不适合。对于纯逻辑错误、算法选择、架构设计类的建议,ESLint 和 tsc 基本无能为力。这个流程最适合的场景是:类型错误修复、API 用法修正、代码风格调整、React hooks 依赖修复这类“规则可验证”的调试任务。对于“这个排序算法是否适合当前数据规模”这类问题,交叉验证帮不上忙,还是要靠人的判断和性能测试。