将 AI 编码代理转变为确定性的 Java Spring 专家

随着 AI 编码代理的兴起,开发人员已开始尝试处理复杂的多步骤升级请求。由于 Spring 是最流行的 Java 框架,开发人员只需使用“请将此应用程序升级到 Spring Boot 4”这样的自然语言命令即可完成应用程序升级,这一设想令人振奋。

但实际上,这种升级方式往往令人失望,尤其是当目标应用程序是一个规模庞大的遗留项目时。开发人员通常需要等待 20 分钟甚至更长时间才能获得初步结果。之后,开发人员通常还需要进行数轮迭代来修复编码和编译错误,更不用说测试用例了。

升级一个贯穿整个应用程序的核心框架需要大量的测试和检查:即使代码能够编译且从技术上看来没有问题,框架的运行方式也可能发生了细微变化,或者升级后新增的功能以一种新颖且微妙的方式运作,这些情况直到运行时才会显现出来。

这种来回往复会从两个方面累积耗时:

  • 假设代码库不依赖于内部共享库或框架,这一过程已耗时 1 到 2 天。而且这还无法保证更改能成功合并(例如 CI 检查可能失败,开发人员可能拒绝 PR 等)。如果没有代码代理,这个过程可能需要多花几天时间,但结果往往更具确定性:人工编写代码通常能正常运行并可被合并。

  • 使用代码生成时,你还需要考虑 token 成本。这包括初始运行和每次迭代。

如果框架升级是为了修复 CVE,时间就显得尤为关键,尤其是需要进行重大升级时。需要尽快部署补丁,以消除任何潜在的安全风险。

示例:升级 Spring Petclinic

以 Spring Petclinic 从 Spring Boot 3.5.x 升级到 Spring Boot 4 为例。Spring Petclinic 应用程序包含的实体(兽医、宠物主人、预约)非常少,结构相对简单且设计合理。使用 AI 从 Spring Boot 3.5.x 迁移到 Spring Boot 4 时,我花了 478,380 个 token 用于规划,再加上 908,900 个 token 用于代码修改……但最终还是失败了。

问题出在哪里?这个编码助手尝试进行了一些我并未要求的更改,例如更新或重命名 Spring Starter 以及导入语句。此外,还出现了若干关于使用已弃用方法的编译器警告,以及一个编译错误。这意味着我需要进行更多轮迭代来修复问题,从而消耗了更多的 token 和时间。

使用 AI 编码代理进行 Spring 升级面临的挑战:正如 Spring Petclinic 示例所示,错误和已弃用的方法问题往往需要多次迭代才能解决。

而这还是从较新版本的 Spring 进行升级的情况,如果我的目标是从更旧的版本(如 2.7.x)升级到 Spring Boot 4,情况会更加糟糕,因为中间的步骤中可能会出现各种问题。

根据我的经验,肯定会出错。这一点值得注意,因为博通(Broadcom)估计,到 2025 年,仍有约 50% 的 Spring Boot 应用运行在 Spring Boot 2.7 或更早版本上。

当升级不可或缺时

工程团队升级到 Spring Boot 新版本的主要原因之一是解决安全问题。在当下,这一点尤为重要——博通正观察到漏洞发现数量激增。如今,许多组织已认识到,升级是其安全准备策略的重要组成部分。

像 Petclinic 这样的演示应用所展示的,升级可能面临麻烦和成本,但在生产环境中,组织比以往任何时候都更迫切地需要进行升级,以解决多个相互依赖的 Git 仓库中的安全问题。在这些情况下,践行持续升级的做法非常有帮助。通过将所有依赖项版本保持在受支持的范围内并使用最新的补丁版本,持续升级的文化可以缩短 CVE 影响您代码的时间窗口,并让您能够使用 Java 和 Spring 的最新优化功能,从而节省基础设施成本。

此外,标准化有助于在所有应用程序中使用相同的 Spring 版本,使您能够在应用程序之间共享内部 Spring 组件。虽然并非总是可行,但一个更加同质化(或至少处于支持周期内)的 Spring 环境,确实有助于日常维护和持续升级,因为相关流程会更加一致。为了推动标准化,您需要考虑大规模运维。因此,真正需要自问的关键问题是:“如何更高效地大规模升级 Spring?”

此外,标准化有助于在所有应用程序中使用相同的 Spring 版本,从而使您能够在不同应用程序之间共享内部 Spring 组件。虽然这并非总是可行,但一个更加统一(或至少仍在支持范围内的)Spring 环境确实有助于后续维护和持续升级,因为相关流程会更加一致。为了推动标准化,您需要考虑大规模运维的问题。因此,真正需要自问的关键问题是:“如何更高效地大规模升级 Spring?”

正如我们在 Petclinic 这个简单示例中所发现的,尽管编码代理能让你更快地编写升级变更,但它们既不完美,也不是免费的。毕竟,它们从定义上来说就是非确定性的,因此无法预测,难以保障一致性,也无法始终提供准确的结果。

因此,核心问题在于:“如何在大规模场景下加速这些基于 AI 的升级,并应用更多正确的代码变更?”根据我的经验,要在大规模场景中取得良好效果,需要为编码代理配备额外的工具,这些工具能够执行类型安全且确定性的重构。

为了理解“人机协同”(HITL)如何改善升级过程,让我们首先了解编码代理的工作原理。

类型感知性的必要性:解决代理的局限性

编码代理是旨在利用大型语言模型(LLM)并基于一组配置好的工具或技能来生成最佳答案的产品。编码代理是利用大型语言模型(LLM)和工具来生成代码及相关产出(如配置和文档),并执行包括编译、运行测试甚至部署代码在内的命令的应用程序。有些人更倾向于使用“harness”(控制框架)一词,而非“编码代理”。

在此语境下,“工具”可以是任何命令行工具(如 Maven 或 Gradle),而“技能”则是包含某些脚本的自然语言描述,我们可以对其进行配置,以强制代理在特定情况下遵循这些脚本。

编程代理在设计时就考虑到了可扩展性;例如,可以通过(远程或本地)MCP 服务器来扩展这些工具。技能的设计旨在简单易用且易于添加;它们本质上只是存储在特定目录中的 Markdown 文件。

一个常见的误解是,开发者认为编程代理会像人类一样对代码库进行语义分析来实施代码更改,从而提高代码质量和准确性。我了解到这种看法并不正确。

默认情况下(Claude、Cursor、Copilot),编码代理无法理解代码库中每个表达式的类型或正确性,这就是为什么有时编码助手会引入编译错误。

您可以通过 MCP Server连接语言服务器协议(LSP)来减少错误——部分 IDE 或开发工具支持这项技术,但除极少数情况外,将其用于执行代码修改并不常见。在 Java 领域,设计确定性代码变更(配方)以解析代码库中每个表达式的类型(是的,就像 IDE 的自动补全机制一样)最灵活的开源技术是 OpenRewrite,它作为 Maven 或 Gradle 插件运行。

将确定性工具与代理可扩展性相结合

为解决这些问题,Tanzu Platform 和 Tanzu Spring 提供了一些基于 OpenRewrite 的 CLI 命令,可帮助编码代理规划和升级 Spring 应用。这些 Tanzu 解决方案将指导您的编码代理规划并执行大规模的增量 Spring 升级。该 CLI 将处理弃用功能、API 变更、属性变更,甚至能处理诸如内部框架和第三方开源库升级等复杂情况——这些库依赖于 Spring 或其传递性依赖,因此必须优先进行升级。

如果您正在使用 Tanzu Platform,最简单的入门方式是在终端中运行“cf repo unpack-skills”,然后让您的编码代理执行 Spring 升级。这将解压一个技能,使您的编码代理能够执行“cf repo upgrade-plan”来规划升级,并执行“cf repo apply-upgrade-plan”来执行计划的第一步——应用代码更改。

或者,您可以使用 Tanzu Spring Essentials,该方案包含对 App Advisor CLI 的访问权限,您只需使用 App Advisor 本地 MCP 服务器,该服务器将指导您的编码代理使用相应的 CLI 命令获取并应用升级计划,但每位开发人员都需要维护 CLI 版本和配置。

有时,工程团队在升级到 Spring 的主要版本之前,需要进行周密的规划。这一规划可能需要移除在新版本中不再受支持的项目,并迁移到兼容的替代方案,例如 Spring Boot 4 中的 Spock 集成。

此外,可能还存在一些会破坏 API 兼容性的变更,需要额外的背景信息(例如,新增的异常类型,或将方法修饰符从 public 改为 protected),以便应用最合适的重构方案。这只是一个例子,每个组织都需要确定一种通用方法来处理这些兼容性问题,并集中进行解决。

在大规模环境中平衡确定性与非确定性

当您决定如何解决 Spring 升级过程中或任何内部库中的问题时,VMware Tanzu Spring 团队建议遵循以下原则: 如果解决兼容性问题的方案是确定性的,我们建议组织创建自己的配置方案,并使用我们的 Tanzu CLI 工具配置常见的迁移模式(例如,从 Spock 迁移到 JUnit)。

与要求开发人员使用 AI 编码代理采用简单粗暴的方法相比,这种解决方案在规模化部署时成本要低得多,因为使用 App Advisor CLI,无论是用于测试还是跨数百个Git仓库应用更改,都不需要消耗 token。

反之,如果解决兼容性问题的方案是非确定性的,我们建议您在 Git 仓库中集中创建一组技能集,供您的编码代理在应用 Spring Boot 升级之前或之后按需调用。

现在,如果您考虑大规模升级 Spring,该过程不应由任何开发人员手动触发。它应由您的 CI/CD 管道自动触发,然后通过 CLI 直接配置,或者如果您拥有可能需要的额外技能,也可通过 AI 编码代理进行配置。通过这种设置,通过这种设置,您可以避免为重复性任务消耗大量 token——这些任务不一定能带来好的结果,而可以将令牌资源节省下来,用于解决实际的业务问题,而不仅仅是升级框架。

作者 Raquel 是 Broadcom 的高级产品经理。她在 Java、CI/CD 开发工具以及内部开发平台(IDP)方面拥有丰富的经验。

请使用浏览器的分享功能分享到微信等