AI生成代码是蒙对的?用验证闭环把偶然正确变成稳定交付
“哼它只是碰巧蒙对而已下次我要选我擅长的。”如果你经常用 AI 编程助手写代码对这句话一定不陌生。可能是同事吐槽 Copilot 突然给出了一段能跑的代码语气里带着怀疑“它怎么会的是不是从哪里抄的”也可能是你自己在调试时AI 连续几次给错方案最后一次突然对了你嘴上不说心里已经在想这大概率是碰运气。这个场景背后其实是很多开发者对 AI 编程工具的深层疑虑它给出的正确结果到底是真的理解了需求还是基于概率“蒙”出来的我的判断是在现有大语言模型的机制下AI 生成代码的“正确”和“蒙对”本来就没有本质区别它们都来自同一个概率采样过程。真正拉开差距的不是模型内部是否“理解”而是开发者有没有建立一套验证闭环把一次偶然正确变成稳定可控的产出。换句话说AI 可以偶尔蒙对但工程团队不能靠“蒙”交付。这篇文章不打算讨论“AI 是否有意识”这种哲学问题。我更想从一个实际开发者的角度把这件事拆成几个可以操作的问题为什么 AI 给出的代码看上去是“蒙对的”“蒙对”和“真会”之间差在哪几个环节怎么设计一套流程让 AI 每次都在你擅长的领域里帮你而不是在你不熟悉的领域里“自由发挥”有哪些场景风险太高不适合让 AI 尝试哪怕它看起来很自信读完这篇文章你会得到一套可以落地到日常开发中的方法如何验证 AI 生成代码、如何给 AI 布置它真正擅长的任务、如何在高风险场景里守住安全边界。这不是一篇纯理论文章里面的代码和流程你可以在下一个任务里直接用。1. 为什么会觉得“它是蒙对的”在拆解方法之前先搞清楚“蒙对”这个感觉是怎么来的。如果你不理解 AI 工具的工作原理就很难设计出正确的使用策略。1.1 大语言模型生成代码的本质先从最基础的机制说起。大语言模型LLM在生成代码时本质上是在做一件事给定前面的 token你可以理解为“词元”预测下一个 token 最可能是什么然后不断重复这个过程。例如你输入注释# 计算两个数的最大公约数模型会基于训练时见过的海量代码片段计算下一个 token 的概率分布。它可能会给def一个很高的概率给import一个略低的概率给while另一个概率。最终生成哪一条路径取决于采样策略。这种机制意味着模型给出的结果是“最像样的答案”不是“经过验证的答案”。这句话很关键。在传统工具链里编译器的输出是确定的你写了int a 1编译器绝不会自作主张把它改成String a 1。但 LLM 不是编译器它生成的是“看起来合理的代码”这种合理性来自统计规律而不是来自对程序语义的精确推导。所以你会遇到一种奇特的现象AI 生成了一段代码语法完全正确运行结果也正确但你追问它“为什么这么做”它给出的解释却是错的。这在技术圈叫“幻觉的次生形态”——代码对解释错。这说明模型在生成时并没有一个完整的因果推理链条它的“解释”同样是后验生成的文本。1.2 训练数据的模式记忆还有一个原因加剧了“蒙对”的感觉大语言模型的训练数据里包含大量高质量代码。对于常见的任务比如二分查找、快速排序、正则匹配、JWT 解析、数据库 CRUD模型早就见过成千上万种写法。它生成“正确代码”的过程更像是在做模式匹配而不是现场思考。这不一定是坏事。恰恰是这种模式记忆能力让 AI 工具在“常见任务”上表现稳定。问题在于模型不会主动告诉你“这段代码我见过很多次有把握”还是“这段代码我凭感觉拼的你最好自己测”。它给所有回答都配上流畅的文本和自然的缩进导致用户很难从输出本身判断可信度。1.3 “蒙对”的心理学视角从使用者的角度看“蒙对”的感觉通常出现在两种场景第一你让它做一件不常见、边界模糊、需要大量领域知识的事它却给出了一个可运行的方案。这时你会本能地想这么复杂的场景它怎么可能理解肯定是碰巧撞上了。第二它连续几次给出错误方案修改参数后突然通过测试。这时你更倾向于把它归因为随机性而不是模型的能力。这两种场景其实都在提示你AI 工具的工作方式和人类工程师“先理解再编码”的路径完全不同。人类工程师如果答对一道题通常能复述推导过程而 AI 可能只给出一个概率最大的结果无法提供可追溯的“解题思路”。这种不可追溯性是“蒙对”感的重要来源。2. 一次正确和“真会”之间差在哪既然 AI 的“正确”和“蒙对”在生成机制上没有本质区别那在工程实践里我们如何区分我认为关键不在“生成”阶段而在“验证”阶段。2.1 一个开发者的真实场景假设你正在做一个电商订单导出功能要求把订单数据按时间范围导出为 CSV 文件。你让 AI 助手生成核心代码它很快给出一段 Python 实现import csv from datetime import datetime def export_orders_to_csv(orders, file_path): with open(file_path, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([order_id, user_id, amount, created_at]) for order in orders: writer.writerow([ order[order_id], order[user_id], order[amount], order[created_at].strftime(%Y-%m-%d %H:%M:%S), ])你试了一下数据能导出来CSV 也能打开一切正常。于是你心里开始嘀咕它是真的理解了 CSV 导出要注意newline还是只是碰巧生成了一段能跑的代码要回答这个问题不能靠“感觉”要设计几个测试订单列表为空函数是否正常创建带表头的文件created_at字段是否为None此时会不会抛异常文件路径不存在目录不存在函数是否能自动创建目录amount是字符串12.5而不是数字会不会有问题写入大量数据比如 10 万行时性能是否可接受如果 AI 生成的这段代码能通过上述 5 个测试那它可以算作“在你的测试范围内是可靠的”。如果它只通过了第一个测试那它就是“碰巧在某个输入下正确”。2.2 “蒙对”与“真会”的对比维度碰巧正确蒙对测试覆盖下的可靠生成机制概率采样偶然匹配经过多输入验证行为符合预期适用范围只覆盖单一样例覆盖已知边界条件可解释性无法说明为什么正确可以通过测试用例回溯行为可维护性修改需求后大概率出错有测试保护改动后能反馈工程价值低不能作为交付依据高可以作为交付依据表格里的对比核心不是“AI 是否理解”而是“你的工程系统是否对 AI 的输出形成了约束”。测试就是一种约束。没有约束的单次正确本质上就是蒙对有约束的正确才是工程意义上的可靠。2.3 小结论对开发者来说纠结“AI 是不是真的懂了”没有太大意义。更有价值的思考方式是我能不能用低成本的手段把 AI 的一次正确变成可重复的、可回归的正确如果能那它是不是“真会”就不重要了——你的工程系统已经接管了正确性。3. 把“碰巧”变成“必然”最小验证闭环现在我们进入可操作的部分。怎么设计一个流程让 AI 生成的代码不再是“碰巧正确”答案是建立一套最小验证闭环。3.1 最小验证闭环包含四步第一步生成代码。让 AI 根据你描述的需求生成代码片段。第二步静态检查。把代码交给 linter、类型检查器把低级错误先过滤掉。第三步动态验证。运行单元测试或集成测试覆盖核心逻辑和边界条件。第四步人工评审。测试通过之后让团队里负责这块业务的开发者做一次代码评审确认 AI 生成的代码和现有架构一致没有隐藏的坏味道。这个闭环看起来很朴素但很多开发者在真实工作中恰恰会跳过其中某一步。最常见的做法是AI 生成代码本地跑一下主流程能跑就提交了。等到代码上线后才发现边界条件没处理或者和旧数据不兼容。3.2 用一个最小示例落地闭环假设你要写一个函数把时间字符串转成时间戳。AI 给出的代码如下from datetime import datetime def to_timestamp(time_str: str) - int: dt datetime.strptime(time_str, %Y-%m-%d %H:%M:%S) return int(dt.timestamp())先不做任何信任判断直接进入验证闭环。静态检查ruff check to_timestamp.py mypy to_timestamp.py动态验证# test_to_timestamp.py from to_timestamp import to_timestamp def test_normal_time(): assert to_timestamp(2024-01-01 00:00:00) 1704067200 def test_leap_year_time(): assert to_timestamp(2024-02-29 12:00:00) 0 def test_invalid_format(): try: to_timestamp(2024/01/01 00:00:00) except ValueError: pass else: raise AssertionError(should raise ValueError for invalid format)运行命令pytest test_to_timestamp.py -v如果三个测试全部通过这段代码在你的测试范围内就是可靠的。此时你还可以继续补充测试时区问题、time_str为None、输入字符串带时区后缀、时间戳溢出等。这个例子的关键在于你不是在验证 AI 的能力你是在验证 AI 的输出结果是否符合你的需求。这两者有本质区别。前者你无法控制后者你可以通过测试用例不断完善。3.3 把验证闭环嵌入工作流单次验证还不够更好的方式是把验证闭环沉淀成自动化流程。例如在 GitHub Actions 中配置一个最简单的工作流每次 push 代码后自动运行测试# .github/workflows/ci.yml name: CI on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv5 with: python-version: 3.12 - run: pip install -r requirements.txt - run: ruff check . - run: pytest -v这段配置会帮你把“每次代码都自动测试”这件事变成硬性约束。AI 生成的代码提交到仓库后如果 CI 跑不过它就不会被合并。此时“蒙对”与否已经不影响交付质量了因为验证替你兜底。这也是我在这篇文章里最想传达的判断对于 AI 生成代码验证体系的价值远大于提示词技巧。与其花时间研究“如何让 AI 一次写对”不如把精力花在“如何快速验证 AI 写的是否正确”。后者才是工程上可控的杠杆。4. 下次我要选它擅长的任务边界设计回到标题里那句话“下次我要选我擅长的。”这句话如果把主语从“人”换成“AI”其实也完全成立。AI 编程工具有它擅长的任务也有它非常不擅长的任务。高效使用 AI 的秘诀不只是“会用提示词”更是“会判断哪些任务适合交给 AI哪些任务必须留给自己”。4.1 AI 擅长什么从实际经验来看AI 编程工具在下面这几类任务上表现稳定样板代码生成。比如创建标准的 REST 接口、写 DTO、生成 getter/setter。通用算法实现。排序、查找、字符串处理、图遍历这类有明确输入输出定义的算法。单文件小功能。把一段逻辑封装成一个函数输入输出清晰。常见框架的固定写法。比如 Spring Boot 的 Controller、MyBatis 的 Mapper、React 的组件骨架。从注释生成代码。注释写得越具体代码的确定性越高。代码翻译。把 Python 代码翻译成 Java 代码或把旧语法改写成新语法。这些任务的共同特点是边界清晰、有大量训练语料覆盖、单次输出规模小、失败后影响有限。4.2 AI 不擅长什么反过来以下任务交给 AI 要非常谨慎新发布版本的 API 用法。模型的训练数据存在截止时间新版 API 的签名变化它未必知道。深层业务逻辑。涉及复杂的权限判断、多步事务、分布式一致性AI 很难在单次生成里考虑周全。历史遗留项目的隐含规则。老项目里通常有大量“文档没写但代码里体现了”的约定AI 很难从上下文中猜到。安全敏感代码。认证、加密、支付回调验签、SQL 拼接防注入这些场景正确性要求极高一旦出错影响面大。数据迁移类脚本。生产环境的数据迁移脚本需要经过严格评审不能直接信任 AI 生成的版本。一个实用的判断标准如果你自己都无法写出足够具体的验收用例就不适合让 AI 独立生成核心逻辑。因为验证是闭环里最关键的一环你自己都定义不清楚“正确”是什么就更无法判断 AI 的输出对不对了。4.3 把大任务拆成 AI 擅长的小任务所以“选它擅长的”并不是一句情绪化的抱怨而是一种任务拆解策略。看一个例子。假设你要实现一个功能用户上传 Excel 文件后端解析后按模板写入数据库并给管理员发送通知邮件。如果你把这个需求完整抛给 AI说“帮我实现这个功能”它会生成一大段代码里面包含 Excel 解析、字段校验、数据库操作、邮件发送。这段代码看着完整但一旦出问题你很难定位是哪个环节错了。更稳妥的做法是把它拆成几个 AI 擅长的小任务写一个 Excel 解析函数给定文件路径返回表格数据。写一个字段校验函数对解析结果逐行校验。写一个数据库批量插入函数。写一个邮件通知函数给定收件人和正文。每个小函数边界清晰、验收简单、测试好写。AI 在每个小任务上的成功率会明显高于一个大任务。这也正是题目的启发作为人类工程师你的优势不是“写代码比 AI 快”而是“知道哪些任务该交给 AI哪些任务该留给自己并且能设计出验证方案”。选对任务边界比盲目要求“AI 全都会”重要得多。5. 实战让 AI 在“擅长区”完成一个完整功能前面讲了方法论这一节用一个完整示例把“验证闭环 任务拆解”落到实操。场景假设你收到一个需求需要写一个函数从给定的日志文本中提取所有 IPv4 地址并按出现次数从高到低排序。这个需求边界清晰、输入输出明确、算法复杂度不高很适合交给 AI。第一步给 AI 一个清晰的任务描述而不是含糊的需求请帮我写一个 Python 函数 extract_ipv4(text: str) - list[tuple[str, int]] 功能是从输入的日志文本中提取所有合法的 IPv4 地址统计每个 IPv4 的出现次数 按出现次数从高到低排序次数相同时按地址字符串升序排序。 注意只提取真正的 IPv4不考虑 IPv6每行可能有多个 IP。第二步AI 生成代码如下import re from collections import Counter def extract_ipv4(text: str) - list[tuple[str, int]]: # 先匹配 0-255 范围内的四段数字 pattern r\b(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\. pattern r(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\. pattern r(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\. pattern r(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]) matches re.findall(pattern, text) # 将每组四个数字用点拼接 ips [..join(m) for m in matches] counter Counter(ips) return sorted(counter.items(), keylambda x: (-x[1], x[0]))第三步设计测试用例。不要只测一种情况要覆盖正常场景和边界场景# test_extract_ipv4.py from extract_ipv4 import extract_ipv4 def test_normal_log(): text 192.168.1.1 is online, 10.0.0.1 failed, 192.168.1.1 retried result extract_ipv4(text) assert result[0] (192.168.1.1, 2) assert result[1] (10.0.0.1, 1) def test_invalid_ipv4_not_matched(): # 999.999.999.999 不是合法 IPv4 text invalid ip 999.999.999.999, valid 8.8.8.8 result extract_ipv4(text) assert result [(8.8.8.8, 1)] def test_sort_by_count_then_address(): text 3.3.3.3 error, 1.1.1.1 error, 3.3.3.3 retry, 2.2.2.2 ok result extract_ipv4(text) counts [x[1] for x in result] assert counts sorted(counts, reverseTrue) def test_edge_value_ipv4(): text min 0.0.0.0 max 255.255.255.255 result extract_ipv4(text) assert len(result) 2第四步运行测试pytest test_extract_ipv4.py -v如果全部通过再做一个额外的对照测试用一份真实的 Nginx 访问日志片段做验证确认提取结果符合人工判断。到这里你可以比较有把握地把这段代码提交了。这个例子完整展示了“选 AI 擅长的任务 用测试接管正确性”的工作方式。AI 负责生成你负责定义验收标准两者配合效率才会真正提高。6. 高风险场景不要赌 AI 的偶然正确前面讲的都是“如何让 AI 更可靠”这一节要泼一盆冷水有几种场景哪怕 AI 给出了自信满满的答案也必须先停下来因为它一旦“蒙错”代价远大于收益。6.1 典型高风险场景第一类生产环境的数据变更。比如 AI 生成一条 SQL要更新线上用户表。这种操作必须遵循“备份-灰度-回滚”的三部曲。AI 生成的 SQL 也许在本地测试环境跑通了但生产环境的数据分布完全不同可能触发锁表可能全表更新可能影响到未预期的行。看一个危险的例子AI 可能生成这样的语句DELETE FROM orders WHERE status cancelled;如果你没有确认cancelled这个状态在业务里是否还有别的含义没有确认表里是否有需要保留的历史数据没有先 SELECT 看一眼影响行数直接执行后果可能是灾难性的。正确的流程是先验证再执行-- 第一步确认影响范围 SELECT COUNT(*) FROM orders WHERE status cancelled; -- 第二步备份受影响数据 CREATE TABLE orders_cancelled_backup_20250101 AS SELECT * FROM orders WHERE status cancelled; -- 第三步在事务中执行删除 BEGIN; DELETE FROM orders WHERE status cancelled; -- 人工确认影响行数与备份一致后再 COMMIT COMMIT;这里不是 AI 不能用而是要加一道“人工确认影响范围”的闸门。任何 AI 生成的、涉及敏感数据变更的 SQL都必须由懂业务的人先看影响范围再做变更。第二类安全相关代码。包括对称加密、JWT 签名验签、OAuth 回调、支付回调验签等。这类代码的正确性无法靠“跑通一次”来证明需要横向对照官方文档、参考权威实现。AI 生成这类代码时可能使用过时的加密库可能忽略密钥管理可能在错误处理上留下安全隐患。第三类大规模重构。AI 可以帮你重命名变量、抽取函数但无法理解系统里隐式的调用契约。比如一个接口被多个服务调用你让 AI 顺手优化了一下返回结构结果下游的某个服务解析失败。这种跨模块影响只有熟悉架构的人才能判断。6.2 高风险场景的守门原则最小权限AI 的执行环境、本地账户、数据库账号只给最小权限不要用管理员身份运行 AI 生成的脚本。先备份任何生产环境变更先备份数据确保有回滚点。灰度执行先在单台机器或小流量环境验证再推全量。有人审批高风险变更必须经过具备权限的人评审不因“AI 生成”而跳过评审。留存记录记录 AI 使用的提示词、生成的代码和人工修改的部分方便事后审计。你可以发现这些原则和“AI 是否蒙对”没有直接关系。它们是工程团队面对任何代码变更都应该有的底线。AI 只是把这个原则的重要性放大了——因为 AI 生成代码的速度快了出错的概率也同步放大了。没有守门机制的团队使用 AI 工具时风险增长会比效率增长更快。7. 常见问题与排查思路在实际使用 AI 编程工具的过程中下面几类问题最常出现。我整理成一张表你在遇到类似情况时可以对照排查。问题现象可能原因排查方式解决方案AI 生成的代码本地能跑提交后 CI 失败本地 Python 版本或依赖版本与 CI 环境不一致检查 CI 日志中的版本信息对比requirements.txt统一 pyproject.toml 或 requirements.txt使用锁文件固定版本AI 改了一行代码另一个功能突然异常AI 只看到了局部上下文没有理解全局依赖查看 git diff检查被改动函数的调用链为被改动的模块补充单元测试涉及跨模块改动时人工评审AI 反复生成同样的错误代码提示词里把正确信息描述成歧义或模型对旧 API 有路径依赖换一种方式描述需求补充输入输出示例在提示词里显式给出“错误示例”和“期望输出”引导模型避开错误模式AI 生成的测试用例太弱全都能过测试用例只覆盖主流程没有覆盖边界和异常审查测试代码用变异测试或第三方工具评估覆盖率补充空值、超长字符串、并发、异常输入等测试场景AI 对框架新版本 API 的用法是错的训练数据里没有新版本 API 信息查阅官方文档确认 API 签名提示词里提供官方文档片段或旧版本对照让 AI 基于上下文生成AI 生成的代码运行结果正确但性能很差模型选择了可读性优先的写法未考虑大数据量场景用性能测试或cProfile分析热点明确提示词要求“注重时间复杂度”并用压力测试验证这些问题有一个共同特征表面上是 AI 的问题本质上是工程流程缺少验证环节。你如果把“验证”前置很多问题根本不会进入交付环节。8. 最佳实践与工程建议前面六节把“为什么”“怎么办”都讲了一遍这一节沉淀几条最值得记住的工程建议方便你在团队里推行 AI 编程工具时直接参考。8.1 建立“代码可信度分级”不是所有 AI 生成代码都值得同等对待。可以按三个等级分类第一级直接可用。通常是样板代码、通用函数、无业务含义的工具类。这类代码经过测试后可以直接合入。第二级需少量修改。AI 生成了主体框架但业务细节需要人工补齐。这类代码建议把 AI 当成“初稿生成器”人工负责精修。第三级仅作参考。涉及核心业务逻辑、安全、数据迁移AI 的输出只能作为思路参考不能直接进入代码库。建议在 Pull Request 描述里标注“这段代码由 AI 生成经过 XX 测试验证”让评审者知道上下文。8.2 沉淀团队级提示词模板个人级提示词优化收益有限团队级提示词模板价值更大。你可以把团队常用的项目结构、代码规范、技术栈版本、常见问题解法整理成一个文档使用时让 AI 基于这份文档生成代码。虽然模型无法动态读取你的团队文档但你可以在提示词里粘贴关键片段效果会比每次重新描述好很多。一个可复用的提示词模板如下请按照以下约束生成代码 1. 技术栈Python 3.12 FastAPI SQLAlchemy 2.0 2. 项目结构controller / service / repository 三层 3. 代码规范使用类型注解函数必须有 docstring禁止使用 noqa 跳过检查 4. 输出要求直接输出完整代码不要解释代码内注释使用中文 5. 参考示例请参考以下已有代码风格 [在这里粘贴团队已有代码片段]这个模板的价值不在“一次生成完美代码”而在于减少 AI 输出和团队规范之间的摩擦降低后续修改成本。8.3 强制加入验证清单在团队里推行一个 AI 生成代码的提交清单可以进行合并请求前自查[ ] AI 生成的代码是否通过了 linter 检查[ ] 是否为关键函数补充了单元测试[ ] 是否覆盖了至少一个边界条件[ ] 涉及数据库变更时是否确认了影响行数和备份策略[ ] 涉及金额、权限、安全逻辑时是否经过人工评审[ ] 是否理解了这段代码的每一条分支而不是“看着没问题”最后一条尤其重要。AI 编程工具最大的陷阱是让你只 review 不思考。当你对 AI 生成的代码感到“好像没问题但又说不出为什么”最好的做法是回到代码本身逐行读懂它。如果你读不懂就说明这段代码不适合由你签字合入。9. 总结与后续实践路径写到这里可以回到最开始的问题了“它只是碰巧蒙对而已”这句话其实提醒了我们三件重要的事第一AI 生成代码的“偶然正确”不是玄学而是大语言模型概率采样机制的自然表现。理解了这一点就不会对 AI 产生不切实际的期待也不会因为一次失败就否定它的价值。第二“蒙对”和“可靠”之间差的不是模型能力而是验证闭环。静态检查、单元测试、CI 自动化、人工评审这四层防护能把一次偶然正确变成稳定产出。验证体系才是 AI 编程时代工程师最值得投入的能力。第三AI 有它擅长的任务边界。把任务拆小、把边界定义清楚、把验收标准前置AI 工具的可用性会大幅提升。而在高风险场景里无论 AI 多自信都要守住备份、灰度、审批这条底线。如果你现在正准备在团队里引入 AI 编程工具我建议你按下面路径实践第一步选一个低风险的内部项目建立最小验证闭环先用起来。 第二步记录一周内 AI 成功和失败的任务类型归纳哪些任务适合 AI、哪些不适合。 第三步把验证清单和提示词模板固化到团队文档里。 第四步再逐步扩展到更复杂的场景但始终保留人工评审环节。AI 编程工具不会消失它对开发效率和工程模式的影响只会越来越大。真正值得焦虑的不是“AI 会不会替代我”而是“当 AI 突然给出一段能跑的代码时你有没有办法判断它到底对不对”。有了验证体系你也可以笑着回它一句“这次算你厉害下次咱们换个项目试试。”
上一篇/下一篇内容由系统自动关联
返回资讯列表 →