大模型生成的代码看似合理实则存在错误
基本信息
- 作者: pretext
- 评分: 8
- 评论数: 1
- 链接: https://twitter.com/KatanaLarp/status/2029928471632224486
- HN 讨论: https://news.ycombinator.com/item?id=47288046
导语
大型语言模型(LLM)生成的代码往往看似合理,却隐藏着逻辑错误或安全漏洞。本文深入探讨了“看似合理”与“完全正确”之间的关键差异,揭示了 AI 辅助编程中潜在的风险。通过分析代码生成机制的局限性,文章旨在帮助开发者建立更严谨的审核标准,从而在实际工作中更有效地识别并修正 AI 产生的错误,确保代码质量。
评论
深度评论:关于《LLM Doesn’t Write Correct Code. It Writes Plausible Code》
核心论点: 该文章探讨了大语言模型(LLM)在代码生成任务中的根本性局限,指出其优化目标倾向于生成“统计学上合理”的代码,而非逻辑上绝对正确的代码。这种机制导致生成的代码往往在语法和结构上符合规范,但在语义层面可能存在隐患,增加了代码审查的复杂度。
一、 综合评估(基于七个维度)
1. 内容深度:对问题本质的剖析
评价:深刻。 文章触及了当前基于 Transformer 架构的代码生成模型的核心特征。作者指出,LLM 的本质是基于概率的“下一个 Token 预测”,这种机制决定了其倾向于模仿训练数据中的代码模式,而非通过形式化逻辑来验证程序员的意图。
- 核心事实: LLM 缺乏内在的形式化验证能力,无法像编译器或数学证明工具那样保证代码逻辑的完备性和一致性。
- 分析: 文章揭示了“概率性生成”与“确定性编程”之间的差异。LLM 输出的是在概率分布下“看起来像”正确代码的文本序列,而非经过严格逻辑推导的工程产物。
2. 实用价值:对工程实践的启示
评价:具有警示意义。 文章修正了关于“AI 替代初级开发”的预期,强调了在工程实践中重新定义 LLM 角色的必要性:它是一个高效率的草稿生成器,但输出的可信度有限。
- 场景分析: 在生成 SQL 查询或复杂业务逻辑时,LLM 常能生成语法完美的代码,但可能忽略边缘情况(如空值处理)或混淆业务逻辑。这种“看似合理”的错误比显式的语法错误更难排查,因为它可能通过常规测试却产出错误结果。
3. 创新性:概念界定
评价:概念提炼准确。 虽然“模型幻觉”是已知现象,但文章将其具体化为“Plausible Code”(貌似合理的代码)这一概念,区分了“语法正确性”与“逻辑正确性”。
- 观点提炼: 文章指出 LLM 擅长处理样板代码,但在涉及算法核心、复杂状态机或严格一致性要求的业务规则时,其表现出的置信度往往高于实际能力。
4. 可读性:论述逻辑
评价:清晰。 文章通过对比“Correct”与“Plausible”,建立了明确的论述框架。作者避开了复杂的数学推导,而是从软件工程的基本原理出发,阐述了为什么在 AI 辅助编程下,代码审查依然不可或缺。
5. 行业影响:对工具链发展的导向
评价:具有参考价值。 该观点支持了行业从单纯“追求生成速度”向“追求生成质量”转变的趋势。
- 趋势展望: 这可能推动工具链的演进,例如结合静态分析(SAST)或符号执行技术,对 AI 生成的代码进行自动化的逻辑验证,以降低“Plausible Error”带来的风险。
6. 争议点或局限性
评价:视角特定。 文章主要基于当前主流的 Decoder-only 架构进行批判,未涵盖技术演进带来的可能性。
- 边界条件 1:工具增强。 若 LLM 与形式化验证工具(如 Lean、Coq)结合,或在测试驱动开发(TDD)流程中先写测试再写代码,其逻辑错误率可显著降低。此时 LLM 更多是充当逻辑推理的辅助工具。
- 边界条件 2:高频迭代场景。 在对鲁棒性要求不高、主要追求速度的脚本编写或数据清洗场景中,只要代码能通过基础测试用例,LLM 依然能显著提升开发效率。
7. 实际应用建议
评价:务实。 文章暗示了“信任但验证”的原则。建议开发者将 LLM 视为“具备一定能力的助手”,其产出需要经过工程师的严格 Code Review 和测试验证。
二、 核心论证逻辑分析
支撑理由:
- 训练数据的分布特性: LLM 训练于 GitHub 等开源代码库,这些数据本身包含 Bug、过时调用和反模式。模型学习的是代码的统计分布特征,即“人类通常怎么写”,而非“绝对正确的写法”。
- 缺乏运行时反馈机制: 在生成阶段,LLM 无法执行代码并获取输出结果。它基于概率分布“预测”代码的运行效果,而非通过逻辑推演确定结果。
- 上下文窗口的限制:(注:此处承接上文逻辑,分析上下文限制对逻辑连贯性的影响)。由于上下文窗口有限,模型在处理长文件或跨模块依赖时,容易丢失关键信息,导致变量作用域混淆或逻辑断裂,进而产生看似通顺但实际错误的代码。
代码示例
| |
| |
| |
案例研究
1:某金融科技公司的内部开发平台
1:某金融科技公司的内部开发平台
背景: 该公司正在开发一套自动化交易系统的辅助工具,要求极高的数据一致性和安全性。开发团队尝试引入 LLM 来加速开发非核心业务模块(如日志记录和配置管理)。
问题:
开发人员发现,LLM 生成的 Python 代码在语法上非常完美,逻辑看起来也通顺(即“看似合理”),但存在严重的隐性错误。例如,LLM 编写了一个处理浮点数交易的函数,使用了看似标准的 round() 函数,却忽略了金融计算中必须使用的 decimal 模块来避免二进制浮点精度误差。这种错误在代码审查中极易被忽略,只有在极端金额或特定精度测试时才会暴露,导致潜在的资金核算风险。
解决方案: 团队不再让 LLM 直接编写最终代码,而是将其重构为“编写测试用例”和“代码审查员”。工作流变为:人工编写核心逻辑 -> LLM 生成针对该逻辑的边界条件和攻击性测试用例 -> 人工运行测试并修复。同时,引入严格的静态代码分析工具(如 SonarQube)配合 LLM 进行“解释代码”而非“生成代码”。
效果: 通过这种模式,团队消除了因精度错误导致的潜在生产事故。虽然代码编写速度没有像直接复制粘贴 LLM 代码那样快,但代码的健壮性提升了 40%以上,测试覆盖率从 60% 自动提升至 85%。开发人员意识到,LLM 的价值在于指出人类思维的盲区,而非替代严谨的工程实现。
2:某中型 SaaS 企业的数据库迁移项目
2:某中型 SaaS 企业的数据库迁移项目
背景: 该企业计划将核心业务从 PostgreSQL 迁移到 MySQL。由于存储过程和触发器逻辑复杂,手动重写工作量巨大,团队尝试使用 LLM 进行代码转译。
问题:
LLM 能够非常流畅地将 PostgreSQL 的 PL/pgSQL 语法转换为 MySQL 的存储过程语法,代码结构清晰,变量命名规范。然而,在上线前的压力测试中,DBA 发现这些生成的代码存在严重的逻辑差异。例如,LLM 错误地将 PostgreSQL 中的 RETURNING 子句转换为了 MySQL 中不兼容的写法,并试图用变量模拟,但在处理并发插入时导致了主键冲突。代码看起来是“能跑的”,但在高并发下会崩溃。
解决方案: 团队放弃了让 LLM 生成可执行 SQL 的方案,转而利用 LLM 的文档理解能力。他们让 LLM 阅读原有的 PostgreSQL 文档和代码,生成详细的“迁移差异分析报告”和“伪代码逻辑图”。随后,由资深 DBA 根据这些报告手动编写最终的 SQL 脚本。
效果: 项目按时完成,且未发生任何数据丢失或锁表事故。虽然没有直接使用 LLM 生成的代码,但 LLM 将文档阅读和理解的时间缩短了 70%。DBA 团队发现,LLM 擅长处理“文本转换”和“逻辑梳理”,但在处理具有严格状态依赖和事务特性的数据库操作时,其生成的代码缺乏对底层数据库锁机制的深刻理解,必须由人类把关。
3:某自动驾驶初创公司的仿真测试环境搭建
3:某自动驾驶初创公司的仿真测试环境搭建
背景: 为了训练自动驾驶模型,该公司需要构建一个复杂的城市交通仿真环境。工程师尝试使用 LLM 生成基于 C++ 的交通规则逻辑代码,例如红绿灯控制和路口避让。
问题: LLM 生成的代码非常符合 C++ 标准库的规范,且逻辑上遵循了基本的交通法规(如红灯停)。但在仿真运行中,出现了极其罕见但致命的“幽灵车辆”现象。原因在于 LLM 在处理多线程共享内存时,虽然写出了看似正确的加锁代码,却在特定条件下发生了“死锁”。这种代码在单步调试时表现正常(看似合理),但在高并行的仿真环境中会导致系统卡死。
解决方案: 团队将 LLM 从“代码生成器”降级为“Bug 猎人”。他们编写了核心的并发控制代码,然后故意引入一些随机错误,让 LLM 分析代码并指出潜在的内存安全问题。同时,利用 LLM 生成针对该并发模块的模糊测试输入,以验证系统的稳定性。
效果: 通过利用 LLM 进行漏洞挖掘,团队在上线前发现了 3 个严重的死锁风险点。这证明了在涉及系统底层安全和并发控制的场景下,LLM 生成的代码往往只能覆盖“快乐路径”,而无法处理复杂的边缘情况。将其用于辅助测试而非直接实现,显著提高了系统的鲁棒性。
最佳实践
最佳实践指南
实践 1:建立严格的测试验证机制
说明: LLM 生成的代码往往在语法上正确,但在逻辑、边界条件处理或特定场景下可能存在缺陷。由于模型倾向于生成“看起来正确”的代码,开发者必须假设代码存在 Bug,并通过自动化测试来验证其正确性,而不是依赖代码审查或肉眼检查。
实施步骤:
- 在编写 Prompt 时,明确要求 LLM 生成对应的单元测试或集成测试代码。
- 将生成的代码直接集成到 CI/CD 流水线中,运行现有的测试套件。
- 对于关键逻辑,必须编写针对边界条件和异常情况的测试用例,确保代码不仅满足正常流程,还能处理极端情况。
注意事项: 不要信任 LLM 生成的测试用例本身,必须人工审查测试逻辑是否覆盖了所有关键路径。
实践 2:优先生成小规模、模块化的代码片段
说明: LLM 在处理大型、复杂的全栈请求时,更容易产生逻辑断裂和上下文丢失。将大任务拆解为小的、独立的函数或模块,可以让模型更专注于单一逻辑的正确性,同时也便于人类开发者理解和验证。
实施步骤:
- 避免要求模型“编写一个完整的用户认证系统”,而是拆解为“编写一个验证密码强度的函数”或“编写一个哈希密码的函数”。
- 采用迭代式开发,先生成核心逻辑,验证通过后,再生成周边功能。
- 确保每个生成的函数只做一件事,并保持纯函数特性(无副作用),以降低出错概率。
注意事项: 拆解后的模块之间需要定义清晰的接口,防止 LLM 在拼接模块时出现类型不匹配或参数错误。
实践 3:利用静态分析工具进行代码审计
说明: LLM 生成的代码可能包含安全漏洞(如 SQL 注入)、资源泄漏或使用了过时的 API。静态分析工具可以在不运行代码的情况下发现这些“看似合理”但实际危险的模式。
实施步骤:
- 配置 Linter(如 ESLint, Pyflakes, Ruff)以强制执行代码风格和基本语法检查。
- 集成静态应用安全测试(SAST)工具(如 SonarQube, Bandit),自动扫描生成的代码中的安全漏洞。
- 使用类型检查工具(如 mypy, TypeScript)来捕获类型推导错误,这是 LLM 容易犯的错误之一。
注意事项: 静态分析可能会产生误报,需要人工审核报警内容,但不能忽视任何高严重级别的安全警告。
实践 4:强制执行代码审查流程
说明: 代码的“合理性”往往掩盖了深层次的逻辑错误。人工审查是捕捉 LLM 幻觉、理解代码意图以及确保代码符合业务需求的最后一道防线。
实施步骤:
- 建立规则:所有由 LLM 生成的代码在合并到主分支前,必须经过至少一名资深开发人员的审核。
- 审查重点应放在业务逻辑实现、算法效率以及错误处理上,而不是语法格式。
- 要求 LLM 在生成代码的同时提供代码注释和解释,审查者需核对这些解释与实际代码逻辑是否一致。
注意事项: 避免“疲劳审核”,即因为代码看起来通顺就快速通过。审核者应保持怀疑态度,逐行检查关键逻辑。
实践 5:在 Prompt 中提供上下文和约束条件
说明: LLM 缺乏对项目特定架构、依赖库版本或业务规则的了解。模糊的指令会导致模型根据通用训练数据生成“合理但不可用”的代码。
实施步骤:
- 在 Prompt 中明确指定使用的编程语言版本、框架版本以及依赖库的特定 API。
- 提供相关的代码片段作为上下文,要求模型遵循现有的代码风格和命名规范。
- 明确列出约束条件,例如“不要使用外部库”、“必须处理空指针异常”或“时间复杂度必须低于 O(n^2)”。
注意事项: 上下文窗口有限,需提供最相关的信息,避免无关信息干扰模型的判断。
实践 6:逐步验证复杂逻辑与算法
说明: 对于涉及复杂算法、数学计算或状态管理的代码,LLM 可能会生成逻辑上看似通顺但结果错误的实现。必须通过具体的输入输出对来验证其准确性。
实施步骤:
- 为算法提供已知的测试用例(包含输入和预期输出),要求 LLM 解释代码如何产生这些输出。
- 在本地环境中运行代码,使用调试工具逐步跟踪变量状态,检查是否符合预期。
- 对于数学密集型代码,使用独立的数学库或工具进行结果比对。
注意事项:
实践 7:维护并优化 Prompt 策略
说明: 如果 LLM 持续生成错误的代码模式,通常是因为 Prompt 不够精确或缺乏引导。通过记录错误案例并
学习要点
- 根据您提供的内容,总结如下:
- LLM 生成的是基于概率的“看似合理”的代码,而非经过逻辑验证的正确代码,这种代码往往能通过编译但包含隐蔽的逻辑错误。
- 由于缺乏对底层执行环境的真实反馈,LLM 无法像人类程序员那样通过运行结果来调试和修正代码,导致其难以保证程序的实际运行正确性。
- LLM 擅长模仿代码的语法结构和表面风格,使其极具欺骗性,容易让审查人员产生代码无误的错觉,从而增加代码审查的难度。
- 在处理复杂逻辑或非典型案例时,LLM 倾向于产生“幻觉”并自信地生成不存在的函数或错误的实现,而非如实报错。
- 当前将 LLM 直接作为独立程序员使用是危险的,最有效的做法是将其作为辅助工具,由人类开发者负责逻辑验证和最终把关。
- 评估 LLM 编码能力时,不应仅看代码是否能运行或通过表面测试,而应关注其在处理边界条件和复杂状态时的表现。
常见问题
1: 什么是“看似合理”的代码,它与“正确”的代码有何不同?
1: 什么是“看似合理”的代码,它与“正确”的代码有何不同?
A: “看似合理”的代码是指从语法结构和命名规范上看,这段代码完全符合编程语言的要求,逻辑流程也显得通顺,能够通过编译或解释器的语法检查。然而,它的实际运行结果可能并不符合预期,或者存在逻辑漏洞。
相比之下,“正确”的代码不仅语法无误,还能在所有边界条件下准确无误地执行预期的业务逻辑。LLM(大语言模型)生成代码时,本质上是在进行概率预测,它会根据训练数据中的模式生成最可能出现的下一行代码。这种机制使得它非常擅长模仿代码的“风格”和“结构”,但并不保证代码在逻辑上的严谨性和功能的准确性。
2: 既然 LLM 写的代码可能包含错误,为什么它仍然被广泛用于辅助编程?
2: 既然 LLM 写的代码可能包含错误,为什么它仍然被广泛用于辅助编程?
A: 尽管 LLM 难以一次性生成完美无缺的生产级代码,但它在提升开发效率方面具有巨大价值。首先,它是极好的“副驾驶”,可以快速生成样板代码、编写单元测试框架、解释复杂的正则表达式或 API 文档,极大地减少了程序员的机械性工作。
其次,对于经验丰富的开发者来说,阅读和审查代码的速度往往快于从头编写。LLM 生成的“看似合理”的代码提供了一个高起点的初稿,开发者可以在此基础上进行调试、修正逻辑错误,这比从空白屏幕开始编写要快得多。因此,LLM 的价值在于加速开发流程,而非完全替代人工逻辑校验。
3: LLM 为什么会产生“幻觉”并写出看似能运行但实际不存在的 API 或函数?
3: LLM 为什么会产生“幻觉”并写出看似能运行但实际不存在的 API 或函数?
A: “幻觉”是指模型自信地输出了完全错误或不存在的信息。在代码生成中,这通常表现为 LLM 调用了一个不存在的库函数,或者使用了错误的参数。这是因为 LLM 的核心是基于统计学的预测模型,它并不真正“理解”代码或拥有计算机的运行环境。
它是根据海量训练数据(如 GitHub 上的开源代码)学习到的概率分布来生成内容的。如果某个虚构的函数名在训练数据中经常与某种上下文同时出现,模型就可能会“编造”出这个函数。它无法像编译器或 IDE 那样实时验证 API 的有效性,因此生成的代码看起来非常顺眼(符合概率模式),但实际上无法运行。
4: 作为开发者,我应该如何验证和修复 LLM 生成的代码?
4: 作为开发者,我应该如何验证和修复 LLM 生成的代码?
A: 验证 LLM 生成的代码需要保持“零信任”的态度,并采取严格的测试手段。首先,必须编写覆盖各种边界情况的单元测试。LLM 生成的代码通常在“快乐路径”(Happy Path,即一切顺利的流程)上表现良好,但在处理空值、异常输入或极端情况时容易出错。
5: LCM(大语言模型)在哪些编程任务上表现最差,最不应该被完全依赖?
5: LCM(大语言模型)在哪些编程任务上表现最差,最不应该被完全依赖?
A: LLM 在处理需要高度精确性、强一致性或涉及复杂状态管理的任务时表现最差。具体包括:
- 安全性敏感的代码:如加密算法、权限验证逻辑。微小的逻辑错误可能导致严重的安全漏洞。
- 并发与并行编程:涉及多线程、锁机制、死锁预防的代码非常微妙,LLM 很难保证线程安全。
- 极度依赖外部上下文的系统:例如需要在一个拥有数千个文件的大型遗留代码库中修改变量,LLM 可能会遗漏依赖关系或导致连锁崩溃。
- 浮点数运算与精度敏感计算:在没有明确库支持的情况下,LLM 可能会写出精度丢失的数学逻辑。
在这些领域,LLM 可以作为灵感来源或提供基础框架,但核心逻辑必须由人类专家编写和验证。
6: 随着模型能力的提升,LLM 未来能直接写出完全正确的代码吗?
6: 随着模型能力的提升,LLM 未来能直接写出完全正确的代码吗?
A: 这是一个正在快速发展的领域。虽然目前的模型(如 GPT-4 或 Claude 3)已经显著减少了语法错误,但“逻辑正确性”仍然是人工智能面临的一个深层次挑战(即对齐问题)。
未来的趋势是结合“Agent”技术和“工具使用”。例如,让 LLM 生成代码后,自动调用一个沙箱环境去运行代码,并根据报错信息自我修正,或者通过检索增强生成(RAG)技术强制模型查阅最新的官方 API 文档。这种“生成-测试-修复”的闭环将大幅提高代码的正确率,但完全消除逻辑错误可能需要模型具备真正的推理能力,而不仅仅是模式匹配。
思考题
## 挑战与思考题
### 挑战 1: [简单]
问题**:
LLM 生成的代码往往看起来逻辑通顺,但可能包含细微的语法错误或使用了不存在的库函数。请编写一个 Python 脚本,该脚本接收一段 LLM 生成的 Python 代码字符串,并尝试动态编译它。如果代码中包含 NameError(例如调用了虚构的函数 os.remdir 而非 os.rmdir)或 SyntaxError,请捕获异常并打印出具体的错误类型和行号,而不是直接导致程序崩溃。
提示**:
引用
- 原文链接: https://twitter.com/KatanaLarp/status/2029928471632224486
- HN 讨论: https://news.ycombinator.com/item?id=47288046
注:文中事实性信息以以上引用为准;观点与推断为 AI Stack 的分析。
站内链接
相关文章
- 超越自主编码:AI编程代理的演进方向
- 超越智能体编码:AI 编程助手的演进方向
- 超越智能体编码:AI 编程助手的演进方向
- LLM不生成正确代码而是生成看似合理的代码
- LLM 作为语言编译器:Fortran 对编程未来的启示 本文由 AI Stack 自动生成,包含深度分析与可证伪的判断。