摘要

一句话总结

本文总结了代码智能体(Coding Agents)对软件工程工作流的深刻影响,指出开发者正从传统的代码编写者转变为“上下文提供者”与系统管理者,并强调了在AI辅助下重构心理模型与代码护栏的重要性。

关键要点

  • 理性的生产力预期:代码智能体不会取代经验丰富的开发者,日常复杂项目中的效率提升约为1.5倍;但在机械性重构(如1-2小时工作缩短至5分钟)、新项目脚手架搭建和一次性数据分析等特定任务中,可达10倍提升。
  • 工作流向CLI倾斜:作者放弃了重度IDE(如VS Code/Cursor),转向以CLI智能体(如Claude Code、Codex CLI)为主,仅将轻量级IDE(如Zed)用于代码导航和审查(尤其是Git diff功能)。
  • 代码组织方式的改变:为了让智能体读取最少的文件就能获取上下文,代码组织更侧重于详尽的文档字符串(docstrings),并将测试用例作为API的示例。
  • 开发者角色的转变:编写代码的工作并未消失,而是转化为使用自然语言详细指定库、接口和权衡方案,开发者正转变为“上下文提供者”和“管理者/导师”。
  • 并行智能体的局限性:除深度研究或受阻于外部慢速API外,并行运行多个智能体意义不大,因为当前瓶颈在于人类提供反馈的速度,而非代码生成速度。
  • 提示词与代码的混合演进:自然语言提示词编写简单但执行慢且昂贵,代码确定性强且运行快。一种常见模式是从“提示词优先”开始,收集边缘情况后,逐步将部分工作流转化为确定性代码(智能体技能)。
  • 护栏(Guardrails)至关重要:强类型语言、编译器反馈以及高级集成测试(或行为驱动开发 BDD)能为智能体提供严格约束,是确保其输出正确性的核心。

风险与不足

  • 心理模型缺失风险:智能体代写代码剥夺了开发者在手动编码时自然建立系统理解的过程,可能导致后期架构决策失误、调试变慢以及无法有效驱动智能体。
  • 上下文偏离风险:智能体无法自行找出完美的上下文(存在递归问题),初始输入上下文越脱离实际任务,智能体越容易脱轨。
  • 精神疲劳与倦怠风险:使用并行智能体或在等待任务完成时频繁切换注意力(如看手机、刷网页),会给大脑带来负担,导致开发者更容易感到倦怠。
  • 生产力负增长风险:在缺乏隐式上下文的复杂遗留项目(Brownfield)中,如果任务说明不足,清理智能体生成的糟糕代码所花费的时间可能比手动编写还要多(生产力降至0.1倍)。

正文

这是我目前对代码智能体(Coding Agents)的一些零散思考。写下这些主要是为了我自己,以便在一两年后回顾时,能对事物变化的惊人速度感到惊叹。

20多年来软件工程领域的最大变革

我写代码将近25年了,代码智能体可能是我经历过的最大的工作流变革。但它们也被过度炒作了,比如有人声称“软件工程已被彻底解决”、承诺取代经验丰富的工程师,或者宣称能带来10到100倍的生产力提升。

我不认为它们会取代经验丰富的开发者,也不相信大多数开发者在日常工作中能获得接近10倍的效率提升。但即使是持续保持约1.5倍的生产力提升,也是一件非常了不起的事情。我认为这种编写软件的新方式将会成为常态。我们仍处于极早期的阶段,几年后我们使用的工具可能会与今天截然不同。

我的新工作流:从IDE转向“智能体优先”

和许多人一样,VS Code中的Copilot自动补全是让我觉得真正有用的首次AI辅助编程体验。我早期尝试过Cursor,但因为效果不佳很快放弃了。随着模型改进,我又尝试了几次。最终模型变得足够好,Cursor成了我的首选编辑器,我的很大一部分代码都是由LLM生成的。但这种情况并没有持续太久,我发现使用像Claude Code这样的CLI(命令行)智能体效率更高。Cursor/VS Code这类IDE并没有增加太多价值,它们主要是占用大量资源的桌面应用,反而成了智能体的阻碍。

今天我的配置依然如此。我使用Codex CLI作为主要的编程智能体,但也会经常切换到Claude并尝试不同的模型。有时我会使用它们各自的桌面应用,因为这能让我更方便地管理多个会话,但对我来说,它们本质上只是CLI的套壳,没有太大区别。我也没发现不同模型之间有巨大差异。有时Codex表现更好,有时是Claude。无论存在什么差异,提供稍微不同的上下文或提示词所带来的影响都远超模型本身的差异。除非提示词极其简单,否则我总是先让智能体在“计划模式(planning mode)”下运行。我仍然使用IDE(zed),但主要是作为一个轻量级界面来导航、理解和审查代码,而不是用来写代码。Git diff现在可能是我眼中IDE最重要的功能。如果在未来几年看到新一波专注于提供代码理解/差异对比、而不包含任何高级编辑功能的IDE,我不会感到惊讶。

我组织代码的方式也发生了改变。我现在更多地思考如何以最适合智能体的方式来组织代码和文档。我直接在代码中添加更全面的文档字符串(docstrings),并将测试作为API和用例的示例。我希望智能体在执行任务时读取尽可能少的文件就能获取必要的上下文。

“我不再写代码了”是事实,但也具有误导性

到目前为止,我已经参与了几个中型项目,期间我没有敲过一行代码。这句话是事实,但也具有误导性,因为工作并没有凭空消失,它只是转移到了一种不同的语言上。

我经常需要极其详尽地指定库、接口、权衡和简化方案。“使用这个包,不要用那个,它已经过时且无人维护了。”“这个抽象太复杂了,用这些基础组件重写它。”“那个接口不具备前瞻性”等等。这依然是传统的软件工程,只是使用了一种更接近自然语言的表达方式。它仍然需要底层的决策和工程知识。对于相当复杂的系统,我无法想象能把经验丰富的工程师从这个闭环中剔除。困难的部分不在于敲击键盘写代码,而在于知道应该构建什么,并在正确的时间理解正确的上下文。

有人可能会争辩说,未来的LLM会非常先进,能够自己找出完美的上下文,但我认为这不会发生。一个智能体能有多“聪明”,直接取决于它的上下文,这就导致了一个递归问题:为了找出完美的上下文,智能体需要完美的上下文来找出上下文。在这个循环中,必须有一个人类专家来管理初始输入,甚至追溯到从训练数据中过滤垃圾信息。初始输入上下文离现实世界的任务越远,智能体就越容易脱轨。真正正在发生的事情是,工程师的日常工作正更多地向“上下文提供者”和“管理者/导师”的角色转变。我预计我们会看到许多公司试图解决“上下文提供者”的问题。

软件开发是构建模型,而不是生成文本

软件开发不仅是产出代码,同样也是构建关于问题的心理模型的过程。这是一个学习的过程。确切的需求很少在前期就完全明确,而实现过程往往正是向我们揭示边缘情况和错误假设的过程。手动编码有一个隐藏的好处:在编写和调试代码时,我们不断地在有意识和无意识地思考和学习这个问题。即使在编写不需要刻意费力的样板代码时,你的大脑也在后台综合相关信息。

如果智能体剥夺了太多这样的循环,我们在短期内交付得更快,但对我们正在构建的东西及其原因的理解就会减少。这种理解上的差距会在后期显现出来,表现为糟糕的架构决策、更慢的调试速度,以及由于缺乏上下文而无法有效驱动智能体。在使用LLM之前,我从未真正意识到这一点。对问题进行建模是我大脑中自动发生的事情,是写代码时一直在后台运行的任务。但有了代码智能体,这种理解不再作为实现的副产品“免费”获得。它必须被刻意、明确地构建,我们可能需要新的工具和工作流来实现这一点。

并行智能体与人类注意力

我很少使用并行智能体,我认为人们有些高估了它们的用处。如果我的任务定义明确,智能体通常不需要5分钟就能完成。我看不出同时启动10个并行任务有什么意义,因为瓶颈不在于写代码,而在于我提供反馈和更多上下文。

更重要的是,并行线程带来的上下文切换会产生精神疲劳。如果你在使用代码智能体时更容易感到倦怠,可能就是这个原因。在等待任务完成时,你开始做其他事情:看浏览器、刷社交媒体、启动或检查另一个智能体、看手机等。这在一天中可能会发生数百次,而我们的大脑并不能很好地应对这种情况。我们都从手机和社交媒体中体会过这个问题。

与其增加并行化,我认为我们可能需要更好的方法来实时参与智能体的工作,并进入心流状态。看着智能体工作应该是一件有趣且吸引人的事。部分原因在于速度:如果模型能快10到100倍,我们等待的时间就会减少,也就没有时间去做别的事了。另一种方法是采用新的界面类型,通过可视化智能体的工作来保持开发者的参与度,并以此帮助建立我们因不亲自写代码而正在失去的理解和心理模型。

不过,并行智能体在某些例外情况下确实非常有用。例如,漫长的“深度研究”循环,或者受阻于缓慢的外部API、速率限制、缓慢的编译等工作流。

提示词与代码的较量

自然语言的模糊性既是福音也是诅咒。我们不想为了生成100行可靠的代码而写1000字的指令;但我们也不想因为前期提供的上下文不足,而被迫多次迭代那些粗制滥造的生成代码。自然语言在指定确切需求时效率低下,类似形式化规范语言或代码会更好。但自然语言的优势在于,它假设每个人都共享一个相似的关于世界/领域/任务的心理模型,从而能够压缩指令。然而,这种共享的世界模型往往并不存在。你经常看到人类为了词语的语义争论不休,对于智能体,我们必须提供那些没有明确包含在其权重中的上下文。

代码是确定且快速的,但冗长且底层。提示词通常更简洁(因为它们具有模糊性并依赖共享的世界模型作为前提),但执行起来更慢且更昂贵。这就引出了一个有趣的设计问题:一个功能(或应用)应该通过提示词、代码,还是介于两者之间的方式来实现?

我最近做了一个个人小工具,它使用代理轮换来获取YouTube视频的音频,通过转录服务进行转录,然后用固定模板重新格式化。在光谱的一端,整个工作流可以是一个提示词。模型可以自己弄清楚如何使用yt-dlp,如何通过住宅代理绕过机器人检测,以及如何调用转录API。我很确信它能一次性完成任务。对于每个新视频,我只需提交一个包含链接的新提示词。但如果没有记忆或缓存的上下文,模型每次都必须重新探索工作流和边缘情况。

在光谱的另一端,我可以写一个大型Python脚本,逐步执行工作流,处理所有边缘情况,仅在转录和总结环节使用LLM。这个版本运行更快、更便宜,且易于确定性调试。但它前期需要耗费更多工程时间,更难维护,并且假设我已经了解了大多数故障模式(这通常是不现实的)。

在这两者之间有许多折中方案。我可以让模型分析之前的运行情况,并将经验教训写入memory.md文件,以避免重复犯错。或者,我可以让它向代码方向靠拢,让它为特定的子步骤生成Python脚本,加上明确的Markdown指令和最佳实践——这基本上就是一个智能体技能(agent skill)的概念。这个过程也可以是迭代的:从“提示词优先”开始,从实际运行中收集反馈和边缘情况,然后将部分工作流转化为确定性代码以降低成本。我预计这将成为一种非常常见的模式。这个循环也可以反向运行:用一个指向廉价模型的简单提示词,来替换一个脆弱的5000行Python脚本,这同样可能非常有用。

护栏胜过直觉(Guardrails beat vibes)

根据我的经验,使用代码智能体的生产力变化范围从0.1倍(净负增长)到10倍不等,具体取决于任务。最大的收益通常出现在对严格正确性要求不高的情况(如快速原型/实验/MVP),或者正确性受到强大护栏严格约束的情况。

以下是几个智能体真正大放异彩、让我看到10倍甚至更高生产力提升的任务示例:

  • 在测试完善的代码库中进行大型机械性重构。 重命名符号、移动文件、更新导入、微调接口等。如果我能说“除了名称/导入之外不要更改测试,并且所有测试必须依然通过”,这通常接近于一次性完成的任务。我见过这类重构从手动需要1-2小时缩短到使用智能体只需约5分钟。
  • 搭建全新项目(Greenfield projects)的脚手架。 其中大部分代码是模型在其训练数据中见过无数次的标准样板代码,例如带有几个标准功能的FastAPI或React应用。
  • 一次性数据分析任务。 具有常见的设置,如加载/解析JSON、Pandas数据处理、DuckDB查询、Matplotlib绘图等。许多数据分析代码本来就是一次性代码,唯一重要的是输出结果,这非常适合智能体。

不幸的是,对于大多数人来说,这些并不是典型的日常工程任务。你不会每天都在搭建新的MVP或重构大型现有代码库(除非你是AI网红)。大多数日常工作都是在具有大量隐式上下文的复杂代码库中进行混乱的遗留项目(Brownfield)开发。在这些场景下,智能体依然极其有用,但我估计平均生产力提升更接近1-2倍,而不是10倍。虽然不常发生,但我确实有过几次智能体彻底失败的经历,因为任务或上下文说明不足,我花在清理输出上的时间比我自己写代码还要多。挑战在于,你很难在前期明确知道智能体到底需要什么上下文,以及它能自己弄清楚什么。

我确实预计,为智能体编程优化的*护栏(guardrails)*将成为编写代码中更重要的一部分。强类型语言和编译器具有优势,因为它们能提供即时反馈。动态语言在合适的工具下依然表现良好,但像mypy这样的可选类型检查器通常很容易被跳过、难以一致地强制执行,并且在提示词中会产生噪音。测试,尤其是能够经受住大型重构并充当某种文档的高级集成测试(或行为规范),也变得非常有帮助。也许我们会看到2000年代流行的行为驱动开发(BDD)在某种程度上的复兴。

结语

如今只有LLM才会写结语了。

关联主题