AI 让我大幅更快地构建软件。你可以在我的 GitHub 上看到:9 月初活动突然激增,启动了多个项目,其中一些还真的发布了。

这一点毋庸置疑。我可以在一天内从想法变成可运行的原型。我可以询问不熟悉的 API 相关问题、生成测试脚手架、在代码库中追踪 bug,还能在不打开二十个浏览器标签页的情况下获得架构方面的第二意见。自 9 月初以来,我用 AI 为 Linux 构建了一个桌面宠物、为客户做了一个库存应用、为我的写作流程做了一个终端工具、一个可行走的 3D 图书馆,以及比我本应启动的更多的实验。

Bar chart of commits per week to Mochi: 5 in the week of August 31, then 332, 288, 174, and 67 in the following weeks
Mochi 每周的提交量。9 月 7 日那一周平均每天约 47 次。

AI 还写了其中大部分代码。

在所有这些发布过程中的某个时刻,我注意到一件令人不安的事:

我生产软件的能力,比我解释自己所生产软件的能力提升得更快。

这让我有点害怕。我不认为使用 AI 会让工作变得虚假。项目能运行,人们也在用。让我害怕的是,我把整个工作流程都围绕一个问题构建——「它能跑吗?」——却几乎从不问另一个问题:「我理解它为什么能跑吗?」

这周我清楚地看到了这个差距有多大。

这个差距是什么样子

Concept chart: project complexity and understanding rise together until AI-assisted building starts, then complexity curves sharply upward while understanding keeps its slow slope

我意识到自己仍然缺少很多计算机科学基础。最大的漏洞在于数据如何存储和搜索:哈希表、集合、索引。我现在有一个客户应用跑在 PostgreSQL 上,而我却说不出一个索引的代价是什么。这可不太妙!

于是我坐下来开始学哈希表。我搞懂了它们如何存储东西,以及当两个键落到同一个槽位时会发生什么。然后我打开了 Two Sum——LeetCode 上著名的第一道「简单」题——我的大脑就满了。那天晚上我就停下了。

我仓库里的软件处理着我目前还无法解释的东西。当写代码的工具比你懂得更多,而你又从不让它展示它的工作过程时,就会发生这种事。

AI 如何掩盖你的知识缺口

在编码助手出现之前,不懂某些东西会带来摩擦。如果我不理解异步 JavaScript、数据库事务、Linux 权限或 Python 打包,我最终会撞上一堵墙。

这堵墙很容易看见。在翻过它之前,你没法继续推进项目。

这堵墙很烦人,但也很有用。我不得不去读文档、检查错误、尝试一些东西,并且错上几次。最终,一个心智模型形成了。

AI 改变了那个反馈循环。现在我可以粘贴一个错误,然后得到:

问题在这里。改这四行。

很多时候那四行确实管用,感觉棒极了。然后你意识到,五分钟前你解释不了这个 bug,五分钟后你依然解释不了。

错误消失了。墙消失了。知识缺口没有。

我以为自己避开了那个陷阱。我避开它的程度远没有我以为的那么好。

调试是我能看到它发生的地方

这两种工作流之间有巨大的差别。第一种:

  1. 有东西坏了。
  2. 把错误粘贴进 AI 助手。
  3. 应用建议的修复。
  4. 它管用了。
  5. 继续构建。
    而第二种:

  6. 有东西坏了。

  7. 稳定地复现它。

  8. 写下我认为正在发生的事。

  9. 弄清楚那个猜测中哪一部分我可以测试。

  10. 检查相关的代码或日志。

  11. 只改一个地方。

  12. 在我真正需要的时候才向 AI 求助。

  13. 理解这个修复为什么管用。

  14. 如果可能的话,加一个回归测试。
    第一种工作流快得多。我想我们现在都明白这一点,但它也完全绕过了我们的批判性思维。你懂吧?就是那个让我们成为优秀工程师的东西。

第二种让我成为更好的开发者。我一直把大部分时间花在第一种上,所以我正在刻意把一些摩擦加回来。

我开始要求自己遵守的四条规则

1. 在揭晓之前先预测

在我问 AI 某个东西为什么坏了之前,我强迫自己写下一条预测。它可能完全错误。有时它错得离谱。但我必须写下类似这样的话:

我认为这个状态更新触发了另一次渲染,而这次渲染重新创建了那个对象,导致这个 effect 再次运行。

或者:

我认为有两个客户端正在从陈旧状态更新这条记录,而第二次写入覆盖了第一次。

然后我去调查,只有在这之后我才会去问模型。现在 AI 的回应是对我推理的反馈。这更接近于身边有一位资深工程师对我说“你找的方向是对的,但你的心智模型在这里略有偏差”,这比一个补丁有价值得多。

2. 不要接受我无法解释的代码

我不需要理解每个依赖项的每个实现细节。没人做得到。但如果 AI 向我的应用添加了一大块重要逻辑,我应该能够回答:

  • 这段代码解决了什么问题?
  • 为什么这样实现?
  • 它做了哪些假设?
  • 失败时会发生什么?
  • 如果我移除它,什么会出问题? 如果我答不上来,我就还没完成。有时我会直接问:

把这段代码解释给我听,就当我是六个月后必须维护它的工程师。

然后我会对照解释去读实际的代码。AI 生成的代码看起来极其自信,而这种自信让那些可疑的决策出奇地容易被略过。

3. 问“什么测试本可以抓住这个问题?”

当某样东西出问题、我终于弄明白原因时,我会尽量不停留在“太好了,修好了”。我会问什么测试本可以在用户之前暴露这个问题。也许是一个单元测试。也许是一个集成测试。也许诚实的答案是,这个故障在该层面上并不现实地可测,那也没关系。重点是去思考这个故障模式。

一个 bug 在它此后改变了系统时才更有价值。否则我就是在交学费把同一课学两遍。

4. 在让 AI 重写代码之前先读代码

这听起来显而易见得令人痛苦,而我并不总是这么做。当你快速推进时,选中一个文件然后说“清理一下这个”是很诱人的。模型返回了更漂亮的东西,测试通过了,你就发布了。

但重构正是大量架构理解所在之处。为什么这个函数与那个对象耦合?为什么这个状态放在这里?为什么这些职责被混在一起?所以现在我会先弄清楚自己对一段代码不满意的地方,然后再让 AI 去改进它。与其说“重构这个”,我希望能这样说:

这个模块同时在做持久化、校验和 UI 状态管理。我认为这些职责应该被分开。给我展示几种重构它的方式,并解释各自的权衡。

我仍然在使用 AI。我只是先做工程决策。

AI 不是问题所在

我不打算停止在开发中使用 AI。那对我来说太荒谬了。(我刚为 Claude Max 付了钱,所以我得让这笔钱花得值。)这些工具让我能探索原本会避开的的技术,在不熟悉的代码库中找到方向,跳过繁琐的脚手架搭建,并完成交付。它们背后的技术也确实让我着迷。我的错误在于假设一个让我更高效的工具也在让我更熟练。有时这两者确实会同时发生。这个月让我看到它们也可能分道扬镳。

这让较新的开发者处于一个奇怪的境地。一个经验相对较少的人现在可以构建出惊人地复杂的软件,这既令人兴奋,又有些令人陶醉。但复杂性并不会仅仅因为生成代码变得更容易就消失。身份验证仍然有安全后果。数据库仍然有一致性模型。网络仍然会失败,用户仍然会做出你从未预料到的事情。

AI 会在你理解这些问题之前就帮你实现解决方案,因此你会比过去的开发者更早接触到高级问题。这就是我最终拥有一个生产数据库却对索引没有心智模型的原因。我不认为答案是远离难题。而是要留意自己何时走进了一个难题。

把 AI 当作老师

ai as teacher

当我在构建常规的东西时,我很乐意让 AI 加速我的工作。当我处于不熟悉的领域时,我会刻意放慢速度。我会提问、阅读实现、阅读文档、做出预测,并检验我的假设。

值得一提的是,哈希表这一课来自 AI。同一个工具,这次针对的是我理解的内容,而不是我能输出的内容。

不是每一行代码都需要变成一课。那会让开发变得痛苦。但每个项目都应该让我学到一些开始时不知道的东西。否则我就是在积累软件,却没有积累任何判断力。

这四条规则没有涵盖从零开始写代码,所以计划里还有一项:每天做一个小问题,完全不用 AI。数一个字符串里的单词。给一个列表去重。二十分钟,从一个空文件开始。

我不断回到的那个问题

如果明天所有 AI 编程助手都消失了,我能维护我今天交付的东西吗?

我不是说能同样快地构建它、记住每一个 API,或者凭记忆复现每一行。我说的是维护它。我能调试它吗?我能解释它的架构吗?我能安全地修改它吗?我能判断一个 AI 生成的方案什么时候是错的吗?

我此刻诚实的答案是我不确定,而我需要弄清楚。所以这就是下一个测试:从我自己的一个项目里挑一个功能,通过阅读代码从头到尾解释它,不用 AI。我在哪里卡住,就是我接下来要学的东西。

我的 GitHub 看起来已经像属于一个比我更好的开发者了——比那个卡在 Two Sum 上的我更好。我想追上它。


无关的开发更新:

看看我的新麻薯设计!!我迫不及待想实现它们。我正在想办法怎么轻松地把它们和原来的精灵图设计互换,而不用重新做他所有的表情动画

mochi