最初发布于metalbear.com。

一月份我们发布了我们的工程团队如何使用 AI。简而言之,AI 在我们工作的边缘地带很有用(在陌生的代码中定位方向、探索方案、编写脚本),但在核心部分用处不大,因为 mirrord 是一个底层 Rust 工具,架构不寻常,通用模型无法真正应对它。我们在文章结尾称 AI 是“软件开发边缘地带的一个强大工具”。

八个月后,我们再次向工程团队提出了同样的问题。此后 AI 已从边缘走向核心:回复的大多数工程师几乎不再手写代码。但仍然得有人阅读所有这些代码,因此增长最多的那部分工作是审查,不仅是审查 pull request,还包括阅读和修正(或重新提示)智能体写出的每一段代码这一内循环。

我其实不怎么写代码了,我只是审查这些垃圾代码,并试着驯服这头野兽。

我们听说你喜欢工具

在第一部分中,工具集中在 ChatGPT、Gemini、Claude Code 和 Cursor 上。我们仍然不强制使用任何特定工具,如今团队使用的工具甚至更不统一了。Claude Code 和 Codex 是最常见的智能体,通过 CLI、桌面应用或编辑器集成使用。除此之外,还有一条长长的尾巴。一些例子:

  • 在 Emacs 中使用 Claude,通过agent-shell,这是一个 Emacs 包,用于在编辑器内运行编码智能体。
  • Amp,一个编码智能体和开发环境,可以在你的本地检出中从终端工作,也可以在它为每个任务启动的远程机器上工作。
  • Orca,一个桌面应用,用于并行运行多个智能体,每个都在自己的 git worktree 中。
  • pi,一个极简的开源智能体框架,可在一个地方运行 Codex、Claude 和 Copilot 模型,并通过插件和你自己的 markdown 文件进行扩展。
  • Graphify,它将代码库转化为智能体可以查询的图,用于让更便宜的 Codex 模型在我们的代码上更准确。
  • 通过llama.cpp在本地运行的 Gemma,用于提交和 rebase 之类的小杂活。
  • Zed同样也一直出现,既因为它的智能体集成,也因为当人类需要编辑或清理智能体写的内容时,它是一个快速、轻量的编辑器。

成本也进入了考量。据一位工程师估计,Fable 消耗的使用额度大约是 Opus 的十倍,而对他们的工作来说质量提升充其量只是中等,所以他们继续用 Opus。另一位工程师试图通过让一个强模型写一份详细的实现指南,再交给一个更便宜的模型来写代码,以此节省 token,因为生成输出的成本高于读取输入(可惜,更便宜的模型写出的代码太差,让节省变得毫无意义,所以这个实验失败了)。

它在哪里有帮助

现在人们几乎用智能体做所有事情:规划、把工单拆成子任务、研究 bug、探索代码库中不熟悉的部分、写代码(既包括边缘部分也包括产品核心)、编写和运行测试、在请人类查看之前预先审查 PR、诊断和修复 CI 失败,以及调查生产事故。一些普遍共识中的用例:

  • 通过描述而不是名称来查找东西。当你处理例如一个不熟悉的 Kubernetes 数据模型时,你往往不知道你要找的东西叫什么,所以没法用 grep 搜索,但你可以描述它。智能体非常擅长这类搜索。
  • 产品之外的代码。构建工具、bash 脚本,以及用于在本地检查我们的基础设施在某个边缘情况下如何表现的临时测试(比如队列如何处理某种不寻常的事件序列)。风险很低,因为出错只会破坏一个构建步骤或一个本地实验,而不是客户运行的东西。我们的xtask构建脚本被描述为“从未有人类之手触碰过”的东西。
  • 状态更新。让 Linear 工单和状态帖子保持最新这一由来已久的苦差事终于可以外包出去了。
  • 关于过去决策的问题。某个组件为什么这样设计,或者某个功能做了什么决定,问智能体往往比找到当初拍板的人更快。
  • 收集和分析杂乱的数据。我们就是这样找出最不稳定的测试,并衡量 CI 变更对运行时长的影响。
  • 本该由脚本完成的工作。对于还没人自动化的重复性任务,让智能体每次去做,比写脚本更慢也更贵,但我们就是懒。

它仍然吃力的地方

Rust 的质量仍是反复被抱怨的问题。智能体写的 Rust 能编译,但往往比实际需要的长得多,还带着不必要的.to_owned(),.clone()和.collect()调用,之后还得有人逐一清理掉。智能体还特别爱写测试,喜欢把代码拆成一个个小函数,好给它们加上一个个小测试。即便是那些在 AI 方向上走得最远的工程师,仍然会亲自审查每一行以及改动的整体结构,而且常常觉得不尽如人意。

智能体还倾向于重复造轮子。问它怎么解决一个问题,它往往会自己造一套方案,而不是去找已有的库或众所周知的做法,所以它的调研只是一个起点,而非答案。因此大多数工程师更愿意亲手做新领域的初步调研。关键在于,工程师要足够深入地理解问题空间,才能对建立在其上的一切做出正确判断。

写 PR 描述/提交信息让我从更高的层面审视问题,这至少能确保我理解了这个解决方案,并能恰当地评判它。

最强烈的意见集中在写作风格上。有几位工程师坚持自己写每一条提交信息、PR 描述和代码注释,因为智能体产出的这类内容太多,而其中讲得通的又太少。那些让智能体起草文档或 RFC 的人,通常最后会把大部分内容重写,因为正如其中一位所说:"AI 生成的文字读起来像文字浆糊。"但亲自动手的最佳理由是,写 PR 描述会迫使你从更高的层面审视问题,这有助于确保你对解决方案的理解足够到位。

AI 生成的文字读起来像文字浆糊。

我们现在基本只做审查

当被问及自 1 月以来他们的使用方式有何变化时,一位工程师描述了更多的并行和更多的疲惫:

我以前一周有 20-40% 的时间在审查,60-80% 在生成代码,现在 95% 都在审查,太累了。

其他人描述的则接近倦怠。现在一个任务在真正完成之前需要更多轮的审查和修正,所以完成感觉比过去更遥远,而且同时跑好几个智能体时,你会同时处于好几个任务的这种状态中。有些人干脆拒绝并行,只跑一条目的明确的智能体线程,并仔细思考返回的结果。

我一次只用 1 条线程,目的非常明确,并且仔细思考。那些智能体编排器之类的东西对我来说太重了,感觉是最快走向倦怠的方式

最难的部分是保持对质量的控制,并维持对系统如何运作的理解。即使是那些不再手写代码的工程师,在代码能跑之后仍然要经过好几轮审查,直到他们理解并测试了每一行。每个人都在努力不让自己变成人肉代理。

不过我们正在尝试寻找解决方案,目前我们希望可以成功自动化第一轮审查。Greptile 会自动审查我们的 pull request(我们目前正在用 AI 对它和 Qodo 做对比评估),我们中很多人会让一个智能体在人工查看之前先审查自己的 PR。

为将来维护它的智能体而构建

人们设计东西的方式也发生了变化。我们中有些人现在构建时就假设代码将由智能体来维护,这在实践中意味着要有详尽的日志,这样之后调试它的智能体才有东西可读(我相信很多人也会感激这种体贴)。有些人会编写个人技能,把自己反复用到的规则和修复方式固化下来。

这也有一个公司层面的版本。1 月的那篇文章展示了我们 mirrord 仓库里的 CLAUDE.md,当时它包含一段很长的架构讲解。它后来变成了一个AGENTS.md,并以 CLAUDE.md 作为指向它的链接,而且它变得更短了:那份走查指南被认为没有必要而被删掉,只留下每个核心组件的一行描述,以及构建、测试和 lint 的命令。现在它与一份公司级的文件并列。MetalBear 的每个人都会从自己的个人~/.claude/CLAUDE.md,其中描述了我们的团队、我们使用的工具以及每个工具的已知怪癖,这样每个智能体都从相同的背景出发。该仓库是私有的,但这个模式足够简单,可以照搬。

一位工程师关于按计划运行的智能体的实验

我们的一位工程师一直在试验按计划而非按请求运行的智能体。一个智能体每晚对 CI 进行分诊。另一个每天早上从 Linear 中挑一个小而开放的 bug,并开一个 PR 来修复它。微小但不关键是一类人们永远抽不出时间去做、而智能体并不介意的工作。

同一位工程师还构建了一个支持机器人,我们的解决方案工程师用它来起草回复,然后由人工检查并发送。在它自信地将一个未经证实的配置描述为“在以往类似案例中已确认可用”之后,它得到了两条固定规则:在从头推理之前先搜索过去的解决方案,并在每个回答的结尾说明它基于什么。

那么,什么变了?

今年 1 月,AI 还是我们工作边缘的一个工具。现在,得益于模型进步、我们的流程成熟,以及工作流变更传播本身所需的时间,对于许多做出回答的工程师来说,AI 编写了大部分代码。随之增长的工作是确保有人在代码发布前理解它。这无疑更累人,希望我们关于此的下一篇博文会讲我们是如何解决这个问题的。