八年来,我一直渴望拥有一套高质量的SQLite开发工具。鉴于SQLite在行业中的重要性1,我长期以来一直感到困惑:为什么没有人投入资源为其打造真正出色的开发者体验2。
大约两周前,经过三个月(每晚、周末和假期)约250小时的辛勤努力,我终于发布了syntaqlite,实现了这个长久以来的愿望。我相信,发生这一切的主要原因是AI编程代理4。
当然,关于AI“一键完成项目”或反驳“AI全是垃圾”的帖子并不少见。我将采取一种截然不同的方式,系统性地剖析我用AI构建syntaqlite的经历,既包括它带来的帮助,也包括它的负面影响。
我会结合项目背景和我的经历进行阐述,以便你能独立评估这种经历的普适性。每当我做出一个论断时,我都会尝试用项目日志、代码转录记录或提交历史中的证据来支持5。
在我参与Perfetto的工作过程中,我维护了一种基于SQLite的查询性能跟踪的语言,称为PerfettoSQL。它基本上就是SQLite,但增加了一些扩展,以改善跟踪查询的体验。在Google内部约有10万行PerfettoSQL代码,它被广泛团队使用。
一门语言获得关注意味着用户也开始期待格式化工具、检查器和编辑器扩展等功能。我曾希望我们能从开源社区中借鉴一些SQLite工具,但随着我深入调研,失望之情愈发强烈。我发现这些工具要么不够可靠,要么速度太慢6,要么不够灵活,无法适应PerfettoSQL。显然,从头开始构建新工具的机遇摆在眼前,但它从来都不是“我们最应该优先处理的事情”。我们一直不情愿地使用现有的工具,却始终渴望更好的解决方案。
另一方面,我也确实有利用业余时间做点什么的选项。我在青少年时期就创建了许多开源项目7,但在大学期间,随着我意识到自己不再有动力,这种热情逐渐消退。维护者角色远不止“把代码发布出去看看会发生什么”那么简单。它包括处理bug、调查崩溃、编写文档、建立社区,以及最重要的是为项目制定发展方向。
然而,开源社区的痒点(具体来说,是自由地做自己想做的事,同时帮助他人的感觉)从未消失。SQLite开发工具项目始终萦绕在我的脑海中,成为“我想做的一件事”。但我一直推迟此事的另一个原因在于:它既困难又枯燥。
如果我要投入个人时间从事这个项目,我不希望构建的工具只能帮助Perfetto,而是希望它能服务于任何SQLite用户8。这意味着必须精确地解析SQL语法,就像SQLite本身一样。
任何面向语言的devtool的核心都是解析器。它将源代码转换为“解析树”,作为其他所有组件所依赖的中心数据结构。如果你的解析器不准确,那么你的格式化工具和检查器最终也会继承这些不准确性;我发现的许多工具都因使用近似SQLite语言的解析器而饱受诟病,而不是精确地表示它。
不幸的是,与其他许多语言不同,SQLite没有正式的规范来描述其解析方式。它也没有暴露稳定的API供其解析器使用。事实上,SQLite的独特之处在于,在其实现中甚至根本不构建解析树9!在我看来,唯一合理的方法是仔细提取SQLite源代码的相关部分,并将其调整为构建我想要的解析器10。
这意味着要深入研究SQLite的源代码,这是一段极其难以理解的代码库。整个项目都是用C语言编写的,风格异常密集;我花了数天时间才理解虚拟表API 11及其实现。试图掌握完整的解析器栈令人望而生畏。
此外,SQLite有400多条规则涵盖了其语言的完整范围。我必须在每条“语法规则”中指定该语法部分如何映射到解析树中的相应节点。这项工作极其重复;每条规则都与其他规则相似,但根据定义,它们又是不同的。
不仅如此,还需要设计并编写测试以确保其正确性,调试错误,处理人们提交的不可避免的错误报告,然后修复这些问题……
多年来,正是从这里,这个想法被扼杀了。对于一个副业项目来说太难了12,持续的动力太枯燥,投入数月时间去做一件可能失败的事情风险太高。
我从2025年初就开始使用编程代理(Aider、Roo Code,自7月起使用Claude Code),它们确实很有用,但从没让我觉得可以信任它们来完成一个严肃的项目。然而,到了2025年底,模型的质量似乎有了显著提升13。与此同时,我在Perfetto中遇到的问题本可以通过可靠的解析器轻松解决。每次变通方案都会在我心中留下同样的想法:也许现在是时候真正动手构建了。
圣诞节期间,我有了一些思考和反思的空间,决定对AI的终极版本进行压力测试:我能否仅使用Claude Code(Max套餐,每月200英镑)通过“ vibe-coding”完成整个项目?
一月份的大部分时间里,我不断迭代,扮演半技术经理的角色,将几乎所有设计和实现都委托给Claude。从功能上讲,我最终达到了一个合理的状态:一个从SQLite源代码中提取的C语言解析器,上面构建了一个格式化工具,支持SQLite语言和PerfettoSQL扩展,并通过Web游乐场提供访问。
但当我在一月底详细审查代码库时,缺点显而易见:代码库完全是意大利面条式的14。我不理解Python源代码提取管道的大部分内容,函数分散在随机文件中,缺乏清晰的结构,还有一些文件长达数千行。它极其脆弱;虽然解决了当前的问题,但它永远无法应对我的更大愿景,更不用说将其集成到Perfetto工具中了。唯一的安慰是,它证明了这种方法的可行性,并生成了500多个测试用例,其中许多我认为可以重复使用。
我决定放弃一切,重新开始,同时将代码库的大部分改为Rust15。我可以看到C语言将使构建验证器等高级组件以及语言服务器实现变得困难。而且,作为一个额外的好处,它还可以让我使用相同的语言进行提取和运行时,而不是在C和Python之间拆分。
更重要的是,我在项目中完全改变了角色。我承担了所有决策的所有权16,并将其更多地用作“超强化自动补全”,在一个更加严格的过程中:预先进行有主见的设计,彻底审查每个更改, eagerly(急切地)修复我所发现的问题,并投资于脚手架(如静态检查、验证和非平凡测试17)以自动检查AI的输出。
二月份,核心功能逐渐成型,最后的冲刺阶段(上游测试验证、编辑器扩展、打包、文档)导致三月中旬发布了0.1版本。
但据我看来,这个时间表是这个故事中最不有趣的部分。我真正想谈的是,如果没有AI,就不会有这些,以及它对我使用它时所付出的代价。
我之前写过,作为一名软件工程师,我最大的弱点之一是在面对一个新项目时容易拖延。尽管当时我没有意识到这一点,但它在构建syntaqlite方面再合适不过了。
AI基本上让我能够搁置我对技术难题的所有疑虑,对我正在构建正确事物的怀疑,以及对开始的不情愿,而是给了我非常具体的问题来解决。与其说“我需要了解SQLite的解析是如何工作的”,不如说“我需要让AI为我建议一种方法,这样我就可以推翻它并构建更好的东西”18。与无休止地在脑海中思考设计相比,我更喜欢与具体的原型和代码一起工作,而AI让我以前所未有的速度达到这一点。一旦我迈出了第一步,每一步都变得更加容易。
事实证明,只要代码是显而易见的,AI在编写代码方面比我更好。如果我能将一个问题分解为“编写具有此行为和参数的函数”或“编写匹配此接口的类”,AI会比我自己更快地构建它,而且至关重要的是,对于未来的读者来说,它的风格可能更直观。它会记录我可能会跳过的事情,以与项目其余部分保持一致的方式布局代码,并坚持你所工作的语言的“标准方言”19。
这种标准化是一把双刃剑。对于任何项目中的绝大多数代码,标准化正是你想要的:可预测、可读、不出所料。但每个项目都有其边缘部分,即价值来自于做一些不明显的事情的地方。对于syntaqlite来说,这就是提取管道和解析器架构。AI本能地倾向于标准化,在这方面它是有害的,而这些正是我需要深入设计的部分,我经常不得不自己动手编写。
但这里有一个转折:使AI在显而易见代码方面表现出色的相同速度也使它在重构方面表现出色。如果你正在使用AI以工业规模生成代码,你必须不断地持续重构20。如果不这样做,事情很快就会失控。这是vibe-coding一个月的核心教训:我没有足够多地重构,代码库变成了我无法理解的东西,我不得不把它全部扔掉。在重写过程中,重构成为了我的工作流程的核心。在每批生成的代码之后,我都会退后一步,问“这丑陋吗?”有时AI可以清理它。其他时候,会有一个大规模抽象,AI看不到,但我可以看到;我会给它方向,让它执行21。如果你有品味,错误方法的成本会大大降低,因为你可以快速重构22。
在我使用AI的所有方式中,研究产生的价值与所花费时间的比例最高。
我以前曾与解释器和解析器合作过,但我从未听说过Wadler-Lindig pretty printing 23。当需要构建格式化工具时,AI给了我一个具体且可操作的课程,从一个我可以理解的角度出发,并指向论文以了解更多。我最终可以自己找到这个,但AI将可能需要一天或两天的阅读压缩成一次集中的对话,在那里我可以问“但为什么这有效?”直到我真的理解了。
这扩展到我从未涉足过的整个领域。我有深厚的C++和Android性能专业知识,但几乎没怎么用到Rust工具或编辑器扩展API。有了AI,这不是问题:基本原理是相同的,术语是相似的,AI弥合了差距24。VS Code扩展本需要我花一天或两天的时间学习API才能开始。有了AI,我在一小时内就有了一个工作扩展。
对于重新熟悉几天前我没有查看过的项目的某些部分25,它也具有不可估量的价值。我可以控制深度:“告诉我有关此组件的信息”用于表面级别的复习,“给我一个详细的线性演练”用于深入研究,“审核此存储库中的不安全用法”以寻找问题。当你频繁切换上下文时,你会很快失去上下文。AI让我按需重新获取它。
除了让项目存在之外,AI也是它如此完整的原因。每个开源项目都有一长串重要但不关键的功能:你知道理论上如何做的事情,但总是将其降低优先级因为核心工作更为紧迫。对于syntaqlite来说,这个列表很长:编辑器扩展、Python绑定、WASM游乐场、文档网站、多个生态系统的打包26。AI让这些变得足够便宜,以至于跳过它们感觉像是错误的权衡。
它还释放了UX方面的心理能量27。与其将所有时间都花在实现上,我可以思考用户的首次体验应该是什么样的:什么样的错误信息实际上可以帮助他们修复SQL,格式化工具的输出默认应该是什么样子,CLI标志是否直观。这些是将工具从人们尝试一次的工具转变为人们持续使用的工具的关键,而AI给了我关心它们的余量。没有AI,我会构建一个更小得多的东西,可能没有编辑器扩展或文档网站。AI不仅让相同的项目更快。它改变了项目是什么。
使用AI编码工具与玩老虎机之间存在令人不安的平行关系28。你发送提示,等待,要么得到很棒的结果,要么得到毫无用处的东西。我发现自己深夜里想要再做一次提示,不断地尝试AI只是想看看会发生什么,即使我知道它可能行不通。沉没成本谬误也起作用了:即使在任务明显不适合它的情况下,我也会坚持下去,告诉自己“也许如果我这次措辞不同的话”。
疲劳反馈循环让它变得更糟29。当我精力充沛时,我可以写出精确、范围明确的提示,并且真正高效。但当我很累时,我的提示会变得模糊,输出会变差,我会再次尝试,在这个过程中变得更加疲惫。在这些情况下,AI可能比我亲自实现某事更慢,但打破循环太难了30。
在项目过程中,有好几次我失去了对代码库的心智模型31。不是整体架构或事物如何组合在一起。而是日常细节,比如什么在哪里,哪些函数调用了哪些函数,以及积累成一个工作系统的小决策。当这种情况发生时,意想不到的问题就会出现,我发现自己完全不知道哪里出了问题。我讨厌这种感觉。
更深层次的问题是,失去联系造成了沟通障碍32。当你没有一个关于正在发生的事情的心理线索时,与代理进行有意义的交流就变得不可能了。每次交流都会变得更长、更冗长。不再是“将FooClass更改为执行X”,而是“将执行Bar的东西更改为执行X”。然后代理必须弄清楚Bar是什么,它如何映射到FooClass,有时它会出错33。这正是工程师们一直以来对不了解代码的管理人员要求奇特或不可能的事情的抱怨。但现在你成了那个经理。
解决办法是刻意的:我养成习惯,在代码实现后立即阅读它,并积极参与,看看“如果我做这件事,我会怎么做不同?”。
当然,从某种意义上说,以上所有内容对于几个月前我写的代码也是如此(因此认为AI代码是第一天起的遗产代码),但AI让漂移发生得更快,因为你没有构建最初键入它时形成的相同肌肉记忆。
还有其他一些问题,我只在三个月内逐步发现的。
我发现AI让我在设计决策上拖延34。因为重构很便宜,我总是可以说“我稍后再处理这个问题”。而且因为AI可以在生成代码的相同工业规模上重构,所以推迟的成本感觉很低。但这并不是:推迟决策会腐蚀我的清晰思考能力,因为代码库在此期间仍然令人困惑。vibe-coding一个月是最极端的版本。是的,我理解了问题,但如果我更严格地提前做出艰难的设计决策,我本可以更快地收敛到正确的架构。
测试创造了一种类似的虚假舒适感35。拥有500多个测试用例感觉令人放心,AI也很容易生成更多。但人类和AI都没有足够的创造力来预见未来会遇到的所有边缘情况;在vibe-coding阶段,有几次我提出了测试用例,意识到某个组件的设计完全错误,需要彻底重做。这是我不信任感和决定放弃一切并从头开始的决定的一个重大贡献。
基本上,我了解到“正常规则”的软件在AI时代仍然适用:如果你没有一个基本的基础(清晰的架构、明确定义的边界),你将永远在出现bug时疲于奔命。
我不断回到的一个问题是,AI对时间的流逝有多么不理解36。它看到代码库处于某种状态,但它不会像人类那样感受时间。我可以告诉你使用API的感觉,它是如何在几个月或几年内演变的,为什么做出某些决策后来又逆转了。
从这个缺乏理解中产生的自然问题是,你要么犯下过去犯过的同样错误,不得不重新学习教训,要么陷入第一次成功避免的新陷阱,从而在长期内减慢你的速度。在我看来,这与为什么失去一位高质量的高级工程师对团队的打击如此之大是类似的问题:他们携带着不存在于别处的历史和背景,并充当周围人的向导。
理论上,你可以尝试通过保持规范和文档的更新来保留这种背景。但我们在AI之前没有这样做是有原因的:详尽地捕捉隐式设计决策在写下它们时是极其昂贵和耗时的。AI可以帮助起草这些文档,但因为没有办法自动验证它是否准确地捕获了重要的内容,所以仍然需要人工审计结果。而这仍然是耗时的。
还有上下文污染问题。你永远不知道关于API A的设计笔记何时会在API B中回响。一致性是让代码库工作的巨大一部分,为此你不仅需要关于你现在正在做的事情的上下文,还需要关于其他以类似方式设计的事情的上下文。决定什么是相关的需要正是机构知识首先提供的判断类型。
回顾以上内容,AI有帮助和有害的模式相当一致。
当我正在处理我已经深刻理解的东西时,AI非常出色。我可以立即查看它的输出,在错误落地之前抓住错误,并以我独自无法管理的速度前进。解析器规则生成是最明显的例子37:我知道每条规则应该产生什么,所以我可以在一两分钟内审查AI的输出并快速迭代。
当我正在处理我可以描述但尚不知道的东西时,AI很好,但需要更多的关注。学习用于格式化工具的瓦德勒 - 林迪格算法就是这样:我可以阐明我想要什么,评估输出是否朝着正确的方向发展,并从AI的解释中学习。但我必须保持参与,不能只是接受它给我的东西。
当我正在处理我不知道我想要什么的东西时,AI在无用和有害之间。项目的架构是最明显的案例:我在早期几天花了数周时间,沿着AI走进死胡同,探索在那一刻感觉有生产力但最终经不起仔细审查的设计。回想起来,我必须考虑如果没有AI,只是自己思考是否会更快。
但仅凭专业技能还不够。即使我深刻理解一个问题,如果任务没有客观的可检查答案,AI仍然会挣扎38。实现有一个正确的答案,至少在局部层面:代码编译,测试通过,输出与你要求的匹配。设计没有。我们已经争论了几十年的OOP,即使它在第一次起飞之后。
具体来说,我发现设计syntaqlite的公共API是这种情况最明显的地方。我在三月初花了数天时间只做API重构,手动修复了任何经验丰富的工程师都会本能地避开但AI完全搞砸了的事情。没有测试或客观指标来判断“这个API是否令人愉快”或“这个API是否有助于用户解决他们的问题”,而这正是编码代理糟糕地处理它的原因。
这让我回到了我对物理着迷的日子,特别是相对论。物理定律在任何小的局部区域看起来都很简单,牛顿式的,但放大来看,时空会以你不能从局部图景中预测的方式弯曲。代码也是如此:在函数或类的层面上,通常有一个明确的正确答案,而AI在那里非常出色。但架构是所有这些局部组件相互作用时发生的事情,你不能通过拼接局部正确的组件来获得良好的全局行为。
我认为知道你在任何时候都在这些轴上的位置是有效使用AI的核心技能。
八年是一个漫长的时光,用来在脑海中携带一个项目。看到这些SQLite工具在仅仅三个月的工作后真正存在并运行是一个巨大的胜利,我完全意识到如果没有AI,它们就不会在这里。
但这个过程的清洁线性成功故事通常不是人们发布的。我浪费了一个月的时间去vibe-coding。我陷入了管理一个我实际上并不理解的代码库的陷阱,我为此付出了彻底重写的代价。
对我来说的收获很简单:AI是实现的无穷力量倍增器,但它是设计的危险替代品。它在给你一个特定技术问题的正确答案方面非常出色,但它没有历史感、品味,或者人类实际上会如何使用你的API的感觉。如果你依赖它来实现软件的“灵魂”,你只会比以前更快地撞墙。
我希望看到其他人更多地做我正在尝试的事情:诚实地详细描述使用这些工具构建真实软件;不只是周末玩具或一次性脚本,而是那种必须经受住用户接触、错误报告和你自己不断变化的思维方式的软件。