教程 / 技巧与窍门 / 完成前先验证
📝 文字 ● 中级 更新于 2026-05-19

完成前先验证——用证据代替断言

"应该可以"和"已验证可以"是两种不同的状态。LingCode 可能会以自信的语气宣告代码完成,但代码未必能编译、测试未必能运行、功能未必真的符合预期。解决方案不是更聪明的模型,而是一种需要明确践行的纪律:先有证据,再做断言。

为什么"看起来没问题"是个陷阱

0

编程 Agent——包括默认设置下的 LingCode——普遍遵循同一个模式:写代码、扫一眼、觉得没问题、宣告完成。这个模式有三种常见的出错方式:

  • 代码无法编译。一个拼错的符号、一个缺失的 import、一个在文件之间漂移的类型。LingCode 从未真正运行编译器——只是觉得代码看起来合理。
  • 测试什么都没跑。新建了测试文件,却没有接入测试运行器。LingCode 宣称"所有测试通过",是因为运行器发现了零个测试,然后以退出码 0 结束。
  • 功能悄无声息。按钮加上了,点击处理器也存在,但处理器在某个 LingCode 没想清楚的条件下提前返回。"我实现了 X"——是的,而 X 在用户走的那条路上什么都不做。

这些都不是模型的失败,而是流程上的失败——在没有运行验证步骤的情况下就声称完成。解决办法是把验证作为说出"完成"、"修好了"、"通过了"这些词的硬性前提条件。

规则,只说一次

1

在 LingCode(或你)说出以下任何一句话之前:

  • "这个完成了。"
  • "我修好了。"
  • "测试通过了。"
  • "构建干净通过。"
  • "可以提交了。"

同一对话中必须有工具调用的输出,显示相关命令已运行并产生了预期结果。不是"我认为它能编译"——要的是构建输出;不是"测试应该能通过"——要的是测试运行器的通过数量。

这就是本教程的全部内容。接下来讲的是如何真正落实它。

四个验证关卡

2

将每一项完成声明对应到能够证明它的命令。如果对话记录中没有这条命令,这个声明就缺乏依据。

声明 必需的证据
"构建通过了。" swift buildxcodebuild buildcargo buildnpm run buildtsc --noEmit——输出以成功结束,且其上没有任何错误。
"测试通过了。" 测试运行器的输出,显示非零数量的测试已运行且失败数为零。"0 个测试,0 次失败"不算通过——那是空跑。
"代码检查干净。" Linter 输出,无警告也无错误。添加抑制注释算作未完成工作的证据,而非完成的证明。
"功能正常工作了。" 手动复现用户路径——实际点击、实际请求、实际输出——并捕获结果(截图、命令输出,或"我运行了 X,观察到了 Y")。

向 LingCode 要证据,而不是断言

3

当 LingCode 完成一项任务并说"这应该可以"或"测试应该能通过"时,正确的跟进只需一句话:

Run the build and tests and paste the output before declaring
done.

这一个提示就能把对话结尾的 AI 回复从叙述性语言("我添加了这个函数,更新了调用处,应该能编译")变为可验证的实证(实际的构建输出)。如果输出显示有错误,你就回到循环中,手里有了真正的线索。如果输出显示成功,你就拿到了证据。

将这句话设为默认的收尾指令,在你开始任何非简单任务时都加上:"说完成之前,先跑构建和测试,把输出粘贴出来。"把它保存为代码片段,或者让它成为你的肌肉记忆。

"零测试"陷阱

4

最尴尬的失败模式:LingCode 写了一个测试,跑了运行器,运行器以退出码 0 结束——因为没有发现任何测试——然后 LingCode 宣称"测试通过了"。

防御这种情况的方法是检查数量,而不是退出码:

Run the tests. Tell me how many tests executed and how many
passed. If zero tests executed, the test file isn't wired in — fix
that first.

测试运行器发现零个测试的常见原因:

  • 文件不在预期的测试目录中。
  • 文件名不匹配运行器的命名规则(*Test.swift*.test.tstest_*.py)。
  • 类名或函数名不以运行器要求的前缀开头(testitdescribe)。
  • 测试 target 未添加到 Xcode/SwiftPM 项目中——文件在磁盘上存在,但并未被编译。

以上每一种情况都会产生一条绿色的"所有测试通过"提示,但它什么都不能说明。

"能编译但什么都不做"陷阱

5

构建绿了、测试也绿了,仍然无法证明行为正确。对"功能正常工作"真正诚实的检验,是手动复现用户路径。在 LingCode 中强制执行这一点的三种轻量方式:

  • 对于 CLI 功能:让 LingCode 用真实输入调用新命令,并展示输出。"运行 lingcode ask --provider claude 'hello',把响应粘贴出来。"
  • 对于 UI 功能:要求截图或运行时日志。如果 LingCode 无法截图,就让它"在新处理器的入口处添加一条 print 语句,运行应用,点击按钮,把日志输出粘贴出来。"
  • 对于 API 功能:要求实际的 HTTP 调用。"用真实的 payload 去 curl 新端点,给我看状态码和响应体。"
"我检查了代码,应该可以"不是证据。要继续追问。多执行一条命令不过几秒钟;相信一个虚假的"完成",则可能要花几个小时在错误的层面排查 bug。

验证发现问题时,不要遮掩

6

验证步骤的职责就是发现问题。当它发现问题时,正确的应对是:

  1. 完整地阅读实际的错误信息。
  2. 诊断根本原因——是哪里出了问题,而不是哪里看起来可疑。
  3. 修复根本原因。
  4. 重新运行验证。循环。

错误的应对方式,按危害程度递增排列:

  • 下次跳过验证。("一般都能通过。")
  • 抑制警告。(添加 // swiftlint:disable// @ts-ignore# noqa,让 linter 对症状闭嘴。)
  • 跳过或削弱失败的测试。(加 .skip、删除断言、降低阈值。)
  • 不管三七二十一宣告完成,继续往前走。

每一种做法都把真实信号变成永久的技术债务。如果 LingCode 提议这样做,要明确反对:"不要抑制——解释为什么失败,然后修好它。"

让验证变得轻松,你才会真的去做

7

只有验证足够省力,它才会成为习惯。三件事能让它变得轻松:

  • 把构建和测试命令加入白名单——参见减少权限提示。如果每次运行 npm testswift build 都需要审批,LingCode(和你)都会跳过它。
  • 使用 hook 自动运行构建——参见添加自定义 hook。一个在每次写入后运行 tsc --noEmitswift build 的 post-edit hook,意味着验证会自动发生,不依赖任何人记得去问。
  • 把标准验证提示写一次存好。将它添加到项目级的 CLAUDE.md 或一个保存好的代码片段中,这样每个会话从一开始就内置了这个纪律。

收尾检查清单

8

在 LingCode 提交代码、发起 PR 或打出"完成"这个词之前,在对话中逐项确认:

  • 构建:运行了构建,输出以干净结束。
  • 测试:运行了测试,N 个测试已执行,全部通过(N > 0)。
  • 行为:手动演练了面向用户的路径,观察到了预期结果。
  • 无抑制:没有添加 ignore、disable 或 skip 来让验证强行通过。

如果有任何一项缺失,工作就还没完成——它处于"待验证"状态。诚实地说清楚当前状态,是软件开发中最省成本的一种修复。

在 LingCode 中使用

9

这个纪律已被封装为一个 skill——把它放入你的 skills 目录,然后让 LingCode 使用"verify before completion",或者直接依赖 LingCode 在检测到"done"/"fixed"/"passing"等声明时自动触发它:

---
name: verification-before-completion
description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs. Triggers: 'this is done', 'I fixed it', 'tests pass', 'it builds clean', 'ready to commit', 'all green', 'ship it'. Actions: require evidence per claim — build output for 'builds', test count + zero failures for 'tests pass', linter output for 'lints clean', manual reproduction for 'feature works'. Anti-patterns: 'should work' without running, 'tests pass' with zero tests run, suppressing/skipping/lowering test thresholds. Discipline: evidence before assertion, always.
---

Verify before claiming done. Required output in the conversation
before saying "done", "fixed", "passing", "ready", or
"works":

1. Build evidence
   Run the project's build command (swift build / xcodebuild build /
   cargo build / npm run build / tsc --noEmit). Output must end
   clean with no errors. Paste the relevant tail.

2. Test evidence
   Run the test runner. Confirm the output shows a NON-ZERO number
   of tests executed and zero failures. "0 tests, 0 failures" is
   empty — diagnose why the runner found nothing.

3. Behavior evidence
   For user-facing features, exercise the actual path (CLI command +
   output, HTTP call + response, button click + log line). Capture
   the observation.

4. No suppression
   If a check failed, do not silence it (no // @ts-ignore, no
   .skip, no disable comments, no lowered thresholds). Fix the root
   cause and re-run.

When the user asks "is it done?" or you'd say "it should work":
stop and run the verifications first. Report what you ran, what
you saw, and only then claim status. If any check is missing, name
the work as "pending verification" — not done.

Cheap when commands are allowlisted in settings.json (see
fewer-permission-prompts skill) and / or wrapped in a post-edit
hook that auto-runs the typecheck.

保存为 ~/.lingcode/skills/verification-before-completion/SKILL.md——关于具体位置以及 skill 如何被发现,请参见安装 skill

获取 LingCode →

下一步