乐于分享
好东西不私藏

为什么 Go 是 AI 辅助软件工程的理想语言

为什么 Go 是 AI 辅助软件工程的理想语言

现如今 AI 编程助手和 agent 已经可以在几秒内生成大量代码。代码产出速度提高以后,开发者的工作重心也随之变化。阅读生成结果、验证逻辑、发现安全问题,以及维护持续演进的代码库,正在占据更多时间。

Google 的 Cameron Balahan 和 Richard Seroter 在最新的博客 《Why Go is an Ideal Language for AI-Assisted Software Engineering》 中讨论了编程语言选择如何影响这套协作方式。他们从工具链、可读性、可靠性和可维护性四个方面展开,解释 Go 为什么适合人类与 AI 共同参与的软件工程。

下面是原文译文。


一段时间以来,软件工程已经发生了一场深刻而根本的转变。过去,大部分代码都由我们手工编写;现在,我们会让 AI 编程助手和 agent 为我们生成大段代码。不过,AI 仍然需要监督,所以阅读生成的代码、整理代码,并验证它是否实现了我们的预期,依然要由人类完成。AI 对生成代码所处的更大范围上下文了解有限,因此,定义系统架构、设计服务之间的边界,以及保证生产环境整体的安全性和可靠性,也仍然是人类的责任。

在这种开发模式下,开发工具中最重要的因素也在发生变化。

从编写转向审查

过去,开发者主要根据一门编程语言是否容易编写来衡量它的生产力。但当编程 agent 可以在几秒内生成数百行语法有效的代码时,人类编写代码的速度已经不再那么重要。现在更重要的是,在代码写好之后对它进行审查、验证和维护。

换句话说,AI 越来越像你的队友。它可能有些特立独行,但终究还是队友。现在最重要的是,我们如何作为一个团队共同协作。

Go 为软件工程而生

恰好,正是围绕团队协作开发的考虑,促使 Rob Pike、Robert Griesemer 和 Ken Thompson 在二十多年前于 Google 创造了 Go 编程语言。当其他语言快速增加功能,试图提供更多表达程序逻辑的方式时,Go 关注的是一个更大的愿景:让语言设计服务于软件工程。

软件工程不同于编程。编程是通过编写和运行代码来解决问题,软件工程则是与其他人协作,设计和实现一个能够长期演进的系统。编程是软件工程的一部分,但也只是一部分。

让语言设计服务于软件工程,需要的不只是一门语言,还需要一个端到端的平台,以及覆盖软件开发生命周期各个环节的工具。它需要一种带有明确主张的简洁性,让整个团队能够采用相同的方式组织、格式化和测试代码。它需要有力的兼容性保证,让你今天编写的代码在十年后不仅仍能运行,而且仍然是好代码。它需要一个强大的生态系统,以及一套能够随团队规模扩展的全局依赖管理系统。同时,它还需要把合理、可靠的安全考虑和安全工具融入所有这些部分。

这些要素共同构成了可扩展、长期团队协作的基础。借助这些基础,我们能够构建这样的系统:即使最初的作者已经离开多年,系统仍然可以维护。现在 AI 已经加入团队,这个基础比以往任何时候都更加重要。

Go 是一个平台

Go 最与众不同的特征之一,是它不只是一门语言,还是一个平台。从诞生之初,Go 就自带一套可靠的端到端工具链,覆盖软件开发生命周期中的各个环节。Go 平台开箱即用地提供了内置的格式化工具、测试框架、依赖管理和高级安全工具,这些都可以直接通过标准工具链使用。这个平台再加上完善的标准库,减少了对复杂外部框架的需求,也提供了无可比拟的一致性基础。

这些功能和工具最初是为了帮助人类而构建的,但事实证明,AI 与人类的需求出人意料地相似。如果要求 AI agent 在没有外部验证的情况下反复重构代码,它的表现可能很快下降,这与人类手工重构代码时的情况很相似。第一次处理也许有 95% 是正确的,但连续处理会让错误率不断累积,并污染上下文窗口,使准确率下降,同时增加 token 成本。有了 Go,AI 模型可以利用平台的端到端工具链,以更快、更便宜、更可靠的方式处理 Go 代码,生成质量更高、更安全、更正确的代码。

这套集成工具还有第二个不那么明显的好处,那就是整个生态系统的一致性。绝大多数 Go 开发者都在使用相同的核心工具,因此,整个社区可以步调一致地向前发展,在 runtime、IDE 和包生态中同时顺畅地采用重要的语言改进。Go 标准库进一步增强了这种统一方式。它减少了不同项目在程序逻辑上的差异,鼓励开发者使用重复、可预测的惯用写法,让开发者和 AI 都能更快地理解代码。这种结构上的统一不仅能帮助人类团队维护大型代码库,还能为大语言模型提供更干净、更标准化的训练数据。

Go 具有良好的可读性

Go 的另一个突出特征,是它把可读性放在可写性之前。Rob、Robert 和 Ken 意识到,开发者阅读现有代码的时间远多于输入代码的时间。在只有人类参与的开发环境中,这种设计理念形成了一种重视简洁、不过度追求巧妙的文化,并且明确拒绝其他语言所推崇的语法魔法。Gopher 经常说,他们喜欢这样一种体验:你永远看不出团队里的某段代码是谁写的,因为所有代码看起来都一样。

在 AI 驱动开发的时代,这种优先考虑阅读的理念成了效率倍增器。过去,开发者个人可能偏爱简短的语法、隐式类型和能够加快原型开发的巧妙捷径;但 agent 工程体验以及与之对应的人工验证循环,需要的恰好相反:可预测性、显式表达和严格的结构。有了 AI,软件开发生命周期中限制速度的瓶颈,已经完全从生成转向验证。如果一门语言提供十几种方式来表达同一种逻辑,AI 模型就不可避免地会生成语法零散、风格随意、拼凑痕迹明显的代码。对人工审查者来说,验证这些代码会变成一项令人疲惫的工作,因为他们还要费力判断代码的意图。

Go 通过始终如一的一致性解决了这个问题。Go 使用内置的 gofmt 工具强制采用唯一的标准格式,并在语言设计上有意限制复杂抽象。无论代码由资深工程师、初级贡献者还是大语言模型编写,看起来都采用相同的形式。当语法完全可以预测时,人类开发者能够更快发现虚构的 API 调用、逻辑缺陷或安全漏洞。这种标准化也延伸到了 Go 的开源生态,模型因而可以使用标准化的数据进行训练,只需更少的尝试就能生成正确、符合 Go 惯用写法的代码。

归根结底,对人类清晰的语言,本身也会让 AI 模型更容易理解。随着 AI 继续加快代码的产出速度,Go 对可读性的坚持保证了我们能够扩展系统,同时不失去理解、验证和安全维护这些系统的能力。

Go 具有可靠性

不过,可读性和开发者生产力只解决了一半问题。一门语言无论多么易读、生产效率多么高,如果最终得到的应用很脆弱、不安全,或者在负载下出现无法预测的行为,它就不适合进入生产环境。

在 Go 中,第一道防线是它的静态类型系统。这套系统为 agent 生成的代码提供了一个自动化安全网。大语言模型经常难以处理跨文件的结构边界和类型一致性,结果可能虚构出不存在的属性,并埋下不会立刻显现的隐蔽 bug。在 Python 这样的动态类型语言中,这些虚构内容经常能够通过基本语法检查,直到系统在特定的生产负载下运行时才崩溃。在 Go 中,编译器会立即拒绝这些错误。如果 AI agent 尝试使用不存在的方法、传入错误类型,或者留下未初始化的变量,代码根本无法通过编译。Go 标志性的编译速度又与这项能力形成了配合。Go 的编译速度比 Java、C#、Rust 以及其他用于生产环境的编译型语言快几个数量级,agent 因而可以在一个效率很高的自我纠错循环中反复改进代码,修复自己的语法和类型错误,最终在交给人类队友审查之前得到语法正确的代码。

除了编译器,Go “自带电池”的理念还解决了 AI 生成代码所固有的一项重要安全风险,也就是软件供应链问题。当大语言模型被要求实现某项功能时,它会依赖自己的训练数据,因而经常建议使用过时、无人维护,甚至带有恶意代码的第三方依赖。Go 完善的标准库会自然地引导 AI 模型使用经过优化、安全并且由官方维护的包,而不是引入外部依赖。这大幅缩小了供应链漏洞的攻击面,也让代码库保持精简和易于维护。

必须使用外部依赖时,Go 的平台基础设施可以保证依赖的完整性。每一个曾经被任何 Go 程序导入的模块,其校验和与缓存副本都会记录在 Go 的校验和数据库和模块镜像中,从而防止中间人攻击,并消除依赖突然消失或被悄悄修改的风险。此外,Go 漏洞数据库和集成式漏洞扫描工具 govulncheck 会跟踪这些依赖中的已知漏洞,并标记调用了存在漏洞的符号的代码。这些工具提供的反馈噪声少,具有很强的可操作性,人类审查者和 AI 都可以利用这些反馈准确修复漏洞。

最后,Go 内置的测试框架和原生模糊测试工具为持续验证提供了一个标准、严格的沙箱。Go 开发者和他们的 AI 队友不必依赖一组东拼西凑的外部测试工具和框架,可以直接使用原生工具链编写和运行可靠的测试。通过运行模糊测试来暴露隐藏在边界条件中的 bug,AI 可以针对随机且不可预测的输入,反复增强自身逻辑。最终形成的是一个高度可靠的软件开发生命周期,代码在进入生产环境之前已经得到充分加固。

Go 易于维护

可读的代码可以帮助你把系统交付到生产环境,可靠的代码可以让系统今天继续在那里运行,但衡量软件系统的真正标准,是它在第二天以及更长时间里的可维护性。代码库是一个不断变化的系统,会自然老化、积累技术债,并且必须不断适应变化的需求。当软件完全由人类开发者编写时,这种维护负担是运营成本中可以预见的一部分。但是,当自主 AI agent 可以随意生成数百个 pull request,或者重构整个服务时,代码库演进的速度和架构偏移的可能性都会大幅增加。

面对这种加速,Go 的主要应对方式是著名的兼容性承诺。在 Go 中,兼容性不只是为了方便,它还是一项重要的安全和运维要求。得益于兼容性承诺,十五年前为 Go 1.0 编写的代码,无需改动就能在最新的 Go 工具链上编译和运行。由于 Go 承诺永远不会破坏向后兼容性——永远不会有 Go 2.0——Go 代码永远不会失效。相反,随着 Go 编译器和 runtime 不断改进,你的代码也会随之变得更好,不需要进行任何修改。只需升级、重新编译,就能获得相应收益。

如果再加上 Go 在运行和部署方面的可移植性,这种长期耐用性会更加突出。Go 可以直接编译成一个不依赖系统组件的静态二进制文件。随着自主 AI agent 越来越多地承担系统管理员的工作,例如启动微服务、执行脚本,以及通过命令行界面与环境交互,这种自包含设计会变得比以往更加重要。Go 编译器可以面向不同操作系统和处理器架构进行交叉编译,因此,这些 AI agent 可以根据需要轻松地为所有目标构建二进制文件,不需要复杂的构建系统。

为了对抗架构偏移,Go 提供了内置的确定性工具,可以大规模重构代码库并完成现代化改造,覆盖的不只是单个代码库,而是整个 Go 生态系统。这些工具包括 Go 官方语言服务器 gopls,以及重新构建的 go fix。新版 go fix 现在加入了 modernizer 的概念。Modernizer 会以确定性的方式把旧代码模式更新为最新的惯用写法和语言功能,让代码保持统一。在大规模使用时,它推动的不只是一份代码,还包括整个 Go 生态系统,使各个库、开源项目和其他第三方代码库保持一致。由于这些工具已经标准化,并且直接内置在 Go 平台中,AI agent 可以利用它们安全地调整包结构、管理依赖和清理技术债,同时不破坏代码库。

最后,Go 通过内置的可观测性和性能调优工具,把这种可维护性直接延伸到了生产环境。Go runtime 开箱即用地提供内置的性能分析和执行跟踪,让开发者可以深入了解应用在负载下的行为。编译器还原生支持基于性能分析数据的优化,它使用来自真实生产环境的 profile,根据生产环境中的实际使用情况编译出经过高度优化的二进制文件。与 AI 编排的部署流水线结合后,这会形成一套高度成熟的闭环优化循环:生产数据可以自动反馈给编译器,用于重新构建和优化系统。

结论

随着开发者编写的代码越来越少,编程语言的选择反而比以往更加重要,这看起来可能有些违反直觉。然而,当代码生成工作交给 AI 后,软件工程的主要瓶颈已经完全从编写速度转向审查、验证和维护的严谨程度。过去优先考虑宽松的原型开发,以及巧妙、隐式快捷方式的语言,现在很难在零散的 agent 输出重压下保持稳定。相比之下,Go 从设计第一天起就在解决大规模长期协作带来的挑战。它优先考虑阅读的清晰性、投入生产环境所需的成熟度,以及整个平台的一致性,恰好提供了容纳 AI 队友高速产出所需的确定性约束,同时不会牺牲可靠性、可维护性或系统完整性。

归根结底,AI 是你最新的队友。它的生产效率极高,同时也需要严格的约束才能顺利完成工作。当你使用 Go 构建系统时,你不只是在编写代码,还在建立一个可靠且能够自我纠错的平台,让人类与 AI 可以在生产系统上安全地协作和迭代。

参考资料

https://developers.googleblog.com/why-go-is-an-ideal-language-for-ai-assisted-software-engineering/


如果这篇文章对你有帮助,欢迎点赞、在看和分享。

我是 Bobby,欢迎关注我,我们下次见。