你的 LLM 不会写出正确代码,它只会写出“看起来合理”的代码
这篇文章用两个由 LLM 驱动的 Rust 项目做案例,论证“生成看起来像对的代码”与“交付真正正确、可验证、能经受约束的系统”之间仍然存在巨大的工程鸿沟。核心论点文章最强的论点是:LLM 更擅长生成“结构上像那么回事”的实现,而不是自动补齐那些决定正确性和性能的关键细节。在数据库案例里,这个差距不是抽象感觉,而是量化…
转载说明:本文转载并精译自 Hōrōshi バガボンド 于 2026-03-06 发布在 Vagabond Research 的文章 Your LLM Doesn't Write Correct Code. It Writes Plausible Code.。StaticFlow 导入日期为 2026-03-08。 你能对一个数据库跑的最简单测试之一,就是: 对 100 行数据做一次主键查找。 SQLite 只要 0.09 毫秒。一个由 LLM 生成的 Rust 重写版要 1,815.43 毫秒。 这不是什么逗号放错位置的小问题!在最基础的数据库操作之一上,这个重写版慢了 20,171 倍。 编辑说明:有几位读者把这个项目和 Turso/libsql 搞混了。两者并不相关。Turso 是在原始 C 版 SQLite 代码库上分叉演进出来的;本文分析的项目,则是由单个开发者主导、从零开始、由 LLM 生成的重写实现。把同样的 benchmark 跑在 Turso 上,性能与 SQLite 的差距在 1.2x 以内,这符合一个成熟 fork 的表现,而不是一个重写项目。 问题在于:这份代码确实能编译。它也确实通过了自己的测试。它也确实能读写正确的 SQLite 文件格式。它的 README 还声称自己支持 MVCC、并发写入、文件兼容性,以及一个可直接替换的 C API。第一眼看上去,它像是一个已经能工作的数据库引擎。 但它并不是! LLM 优化的是“看起来合理”,不是“真正正确”。而在这个案例里,这种“合理”比“正确”慢了大约 20,000 倍。 我写这篇文章,不是站在批评者的位置上,而是站在实践者的位置上。过去 10 多年的职业开发经历里,我最近 6 个月一直在多个项目中把 LLM 深度接入自己的日常工作流。LLM 的确让任何有好奇心、也愿意动手的人,都能更快把想法做出来,这一点我非常喜欢!但与此同时,我硬盘里也积累了大量截图:输出悄悄出错、逻辑自信却错误、代码看起来没问题但经不起推敲。它们都说明了一件事:事情并不总像表面看上去那样。我的结论是,只有当使用者在第一行代码生成之前就先定义好验收标准时,LLM 才最有价值。 顺便说明一下本文选取的项目:这不是在针对任何某位开发者。我并不认识作者本人,也对他没有成见。我之所以选择这些项目,是因为它们是公开的、具有代表性,而且相对容易做 benchmark。我发现的失败模式,是工具产物导致的,不是作者人格导致的。METR 的随机实验和 GitClear 的大规模仓库分析也表明:如果缺少高强度验证,这些问题并不是某一个开发者的偶发现象。这正是我想说明的点。 这篇文章要讲的,就是这个鸿沟在实践里具体长什么样:代码本身、benchmark 数据、另一个用于验证这种模式是否偶然的案例,以及说明这并非孤例的外部研究证据。LLM 会撒谎,数字不会。 我用同一个 C benchmark 程序分别链接了两个库:系统自带的 SQLite,以及这个 Rust 重写版暴露出来的 C API 库。编译参数相同,WAL 模式相同,表结构相同,查询也相同。测试规模是 100 行: 我把 TRANSACTION batch 这一行当作基线,因为它没有其他几行里那么明显的问题,比如没有 WHERE 子句、或者每条语句都单独 sync。在这次跑出来的数据里,这条基线本身已经慢了 298 倍,这意味着即便走的是“最佳路径”,它也仍然远远落后于 SQLite。任何高于 298 倍的地方,都意味着额外存在 bug。 在这条基线之上,最大的差距主要来自两个 bug: 不带事务的 INSERT:1,857 倍,而 batch 模式只有 298 倍。SELECT BY ID:20,171 倍。UPDATE 和 DELETE 也都在 2,800 倍以上。这个模式非常一致:凡是需要数据库去找到某样东西的操作,都会慢得离谱。查询规划器到底错在哪里 我去读了源码。准确说,是根据 benchmark 结果去读了那些我必须读的部分。这个重写版并不小:625 个文件、57.6 万行 Rust 代码。里面有 parser、planner、VDBE bytecode engine、B-tree、pager、WAL。模块名字看起来都“很对”,整体架构看起来也“像那么回事”。但代码里的两个 bug,再加上一组较小的问题叠加在一起,最终放大成了灾难:Bug #1:缺失的 ipk 检查 在 SQLite 里,如果你这样声明一张表:CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, value REAL); 列 id 就会变成内部 rowid 的别名 也就是 B-tree 本身的 key。像 WHERE…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行