=

作者:Julien Dubois

排版:Alan Wang

三位 Java 冠军分享了他们如何利用 GitHub Copilot 解决问题、加快开发进程,并确保人类始终对最终成果负责。

在这里插入图片描述

AI 编程工具能够快速生成代码,但仅凭速度并不能解决软件工程中最棘手的问题。开发者仍然需要提供上下文、验证结果、保障代码的可维护性,并对最终交付的软件负责。

为了了解经验丰富的开发者如何在这些方面取得平衡,我们采访了三位 Java 冠军(Java Champions),他们都有十年以上的 Java 使用经验。他们分享了在生产环境和开源项目中使用 GitHub Copilot 的具体案例,以及如何通过一系列实践,将 AI 生成的内容转化为可靠的软件。

以下是他们分享的实用建议。

认识三位 Java 冠军

  • Vlad Mihalcea:Hypersistence 创始人,《High-Performance Java Persistence》一书的作者。

  • Patrick Baumgartner:42talents 软件工艺专家,拥有 20 多年的 Java 开发经验。

  • Frank Delporte:Azul 高级技术文档工程师,Foojay.io 维护者,拥有 15 年以上的 Java 开发经验。

Vlad Mihalcea

GitHub 主页:@vladmihalcea

GitHub Copilot 最近在哪些方面帮助了你?

我一直在使用它分析、设计和实现 Hypersistence Utils 与 Hypersistence Optimizer 中的各种功能。

例如,我一直想开发一个 Web 应用,用来展示 Optimizer 事件的历史记录,以便我们了解性能优化工作最终如何体现在应用程序的性能上。在 AI 出现之前,我一直把这项任务往后推,因为开发它可能需要几周时间。但使用 Copilot 就像是多了一位新团队成员,因此我借助它创建了这个应用,并计划在下一个版本的 Hypersistence Optimizer 中发布。这项任务原本需要几周时间,如今只花了两天就完成了。

你通常如何使用 GitHub Copilot?你使用的是 IDE 插件,还是 GitHub Copilot 应用?

我在 IntelliJ IDEA 中使用 GitHub Copilot,因为无论是在审查源代码时,还是在调试过程中使用它,都非常方便。

它作为代码助手非常实用,能够加快问题排查的速度,也能帮助我快速构建需要进一步验证的功能原型。

我仍然会审查 AI 生成的计划和代码,因为它的目的是提高我的工作效率,而不是完全取代我。AI 在很多情况下都能给出正确答案,但有时它的回答仍不尽如人意,需要我介入处理。

你能分享一些实用技巧和最佳实践吗?例如,你使用了哪些技能?

我创建了自己的技能,让 Copilot 生成的文本尽可能呈现出我亲自写作的风格。

这项技能通过我已发表的 65 篇文章来学习我的表达风格,因此生成的初稿读起来更像我的博客文章,而不是语言模型写出来的内容。在为这个项目编写文档时,这项技能尤其有用。它可以让大语言模型从我 12 年的博客写作经验中学习,并生成遵循相同原则的示例,例如表达清晰、便于测试和保持简洁。

在整个软件工程职业生涯中,我逐渐认识到,我们花在维护代码上的时间远远多于编写代码的时间。因此,对于现有产品而言,可维护性和稳定性至关重要。为此,我开发了一些技能,帮助 AI 遵循现有的代码实践,符合最小意外原则,避免引入让人感到意外的行为。

另一个建议是:根据任务类型切换模型。对于需要深入思考的任务,我会使用具有 100 万上下文窗口的 Opus,并选择 Extra High 或 Max 推理强度。而对于生成报告、发布说明或检查文档等简单任务,我会使用消耗 Token 更少的轻量级模型。

Patrick Baumgartner

GitHub 主页:@patbaumgartner

你的工作涉及团队辅导。你通常采用什么方法?

我的很多工作都是在业务必须持续正常运转的情况下开展的。我们不可能为了进行转型,就让所有工作停下来六个月。通常,我们会先一起解决眼前的问题,了解团队当前所处的状态,明确希望达到的目标,以及如何判断自己正在取得进步,然后一步一步地朝目标推进。

根据具体情况,这可能涉及软件工艺实践,例如测试驱动开发(TDD)、重构、持续交付和极限编程(XP);也可能涉及使用 Spring Boot、Spring Modulith 进行架构设计和模块化,使用 Spring Security,优化 Java 和 Spring Boot 应用在容器中的运行表现,或者越来越受到关注的智能体工程。

对我而言,最重要的是与团队成员一起工作,共同做出决策。我不希望团队依赖我。理想情况下,经过一段时间之后,他们就不再需要我的帮助,最多只需要我作为一个共同探讨问题、相互启发的伙伴。

这或许也解释了为什么我觉得新一代 AI 工具如此有趣:我喜欢与人合作、学习、尝试新事物和解决问题。而 AI 为我们提供了另一种非常强大的工具,让我们能够更好地完成这些事情。

你能分享一个 GitHub Copilot 最近帮助你的案例吗?

实际上,有三个截然不同的案例改变了我对使用 GitHub Copilot 开展工作的看法。

先从第一个案例说起。

其中一个案例是将 15 份演示文稿中约 900 张教学幻灯片,从 Reveal.js 迁移到 Marp。这些幻灯片并不简单。很多页面包含代码、动画,甚至交互式示例。在授课时,我可以实时修改 JavaScript 代码。更重要的是,这些演示文稿已经经过了实际教学的反复检验,内容流程运转良好。即使丢失一个很小的细节,也可能破坏我在课堂上讲解知识的整体逻辑。

如果手动完成迁移,可能需要一周甚至更长时间。考虑到这项工作如此繁琐,现实中我甚至可能一直拖着不做。

使用 Copilot 后,我采用了不同的方法。我们首先分析现有材料,明确迁移目标,制定迁移计划,设定约束条件,提供参考示例,并明确哪些内容必须保持不变。随后,我们从整体结构调整开始,逐步深入到具体细节。

真正重要的是验证过程。

我们分别生成 Reveal.js 和 Marp 版本的截图,并通过视觉和算法进行比较。我们还开发了一个小型应用,可以将两套幻灯片并排运行,以便手动检查动画效果。除此之外,我们还对比了生成的 PDF 文件。

这意味着,智能体不能仅仅告诉我们“迁移已完成”,我们还拥有能够证明迁移结果的实际证据。

整个迁移过程大约花了我 8 到 10 个小时。我始终对内容、教学意图以及每一项有实质意义的修改负责。Copilot 可以自动修正拼写错误,但如果涉及内容、版本、日期或含义的变化,我们就会进行讨论。

这次经历让我更加确信一个重要原则:可以委派执行工作,但不能转移责任。 我必须对最终结果负责。

能再分享第二个案例吗?

第二个案例与使用 Tamboui 构建终端用户界面应用有关。最初,这个应用在通过 WSL 运行的 Linux 环境中表现完美,而我完全忘记了,有人可能会直接在 Windows CMD 或 PowerShell 中运行它。

后来,真的有用户这么做了。

他们发现,在某些表单中,按下 Enter 键完全没有反应。这也体现了我一直坚信的另一个原则:尽早发布软件,因为只有观察人们如何实际使用它,你才能真正学到东西。

我们使用 Copilot 将用户反馈转化为可复现的 UI 测试。在 Linux 上,Copilot 通过 tmux 操作应用。在与它协作的过程中,我了解了 psmux,并得以在 PowerShell 中执行类似的操作流程。我们复现了问题,创建了本地修复方案,进一步追踪到 Tamboui 中的真正问题,并提交了上游修复,最终该修复被合并。

在这个过程中,测试描述本身变得过于庞大,超出了可用的上下文窗口。因此,我请 Copilot 帮助重新设计测试的表示方式。原本的测试使用冗长的表格,包含标识符、名称和重复的描述信息。Copilot 提出了一种更加紧凑的箭头表示法:步骤 → 步骤 → 输入,只在关键信息处补充额外说明。

这让我感到意外。我们不只是利用 AI 执行任务,还在共同探索如何表示信息,才能更好地支持任务执行。这种表示方式不一定要让人类阅读起来尽可能轻松,但必须保留原有语义,同时提高智能体处理信息的效率。

第三个案例又是什么?

第三个案例与前两个完全不同。

我身边有一位亲近的人需要练习数学,于是我想到:为什么不开发一个简单的学习应用呢?

这款应用涵盖小学一至四年级的加法、减法、乘法、除法以及带余数的除法。但我不想再做一个普通的答题应用。于是,我与 Copilot 一起研究了多种学习方法,包括间隔重复、即时反馈、自适应难度、学习动机、合作学习,以及避免采用暗黑模式的游戏化设计。

有趣的是,在研究过程中,Copilot 还对我最初的一个假设提出了挑战。我一开始希望这款应用能够适用于世界各地的儿童。但研究让我们转而关注实际的学校课程体系。我意识到,不同地区在知识点的引入顺序、运算类型和数字范围方面存在足够大的差异,因此一开始就追求所谓的“通用性”并没有必要。

于是,我们决定先聚焦瑞士,并根据当地的课程安排设计学习进度。由于瑞士是一个多语言国家,这款应用支持德语、法语、意大利语和英语。它还提供双人合作和竞技模式。当用户答错时,应用会解释一种解题方法,并通过间隔重复机制,在后续练习中重新呈现类似的问题。

我特意选择了不需要后端的架构。没有必要将儿童的个人数据收集到服务器上,因此,所有数据都保留在设备本地,应用只需托管在 GitHub Pages 上即可。最初只是一个小实验,如今已经有朋友在使用和分享它。它就这样意外地成为了一个小产品。

这次经历让我看到了 Copilot 的另一种价值:不要只用它来实现你的解决方案,还要在开始构建之前,把它当作一个共同探讨问题的伙伴,帮助你拓展并审视可能的解决方案空间。

你通常如何使用 Copilot?

实际上,我已经不再使用传统的 Copilot 自动补全功能了。

我的工作方式几乎已经完全转向了智能体工作流。

当我需要紧密的视觉反馈循环时,例如仔细查看文档、代码、演示文稿或中间结果,并根据情况随时调整流程时,我会使用 VS Code。

对于执行导向的大型任务,我通常会在终端中使用编码智能体。在这种情况下,我更习惯于将一部分工作委派出去,让它执行,然后检查完整的结果。我选择哪种界面,主要取决于自己需要怎样的反馈循环。

前往 JavaLand 途中,我还通过 GitHub 使用编码智能体工作流,获得了一次很有意思的体验。当时,我正在探索一个想法:开发一个 Maven 插件,用于优化 Spring Boot 自动配置项的加载过程。在大约四个小时的旅途中,我一直通过手机指挥智能体:给它分配任务,检查它的工作成果,思考下一步该做什么,然后发送新的指令。

抵达目的地时,我已经拥有了一个可运行的 Maven 插件、一条 CI 流水线、针对示例应用的性能测试,以及能够证明优化是否真正有效的报告。

这是我第一次采用这种方式工作,我很惊讶它竟然真的行得通。我并不是试图在手机上编写 Java 代码,而是在智能体执行任务的同时,通过审查、决策和引导来推进工作。

当 MCP 服务器能够提供有用的能力或上下文时,我也会使用它们。例如,我使用过 Context7 获取文档,使用过与 Logseq 知识库的集成,也尝试过 Adobe Premiere Pro 的 MCP 集成。

但我不会仅仅因为可以使用 MCP 就使用它。如果已经有合适的命令行工具(CLI),我通常更倾向于使用 CLI,因为在我看来,它更加直接、快捷。

在我的认知中,智能体也不是一个“初级开发人员”,而更像是一个共同探讨问题的伙伴。不过,就像任何刚加入项目的人一样,它首先需要熟悉项目背景。

上下文至关重要。

你能分享一些实用技巧和最佳实践吗?

我最想强调的一点是:停止一次性提示。

对于任何并非特别简单的任务,我都希望在开始执行之前先理解问题。很多时候,我会明确要求智能体先通过提问来了解我的需求。这样做的目的是挖掘那些尚未明确表达的假设和期望。

一个优秀的智能体可以极快地执行一个错误的想法,但这并不意味着走弯路的成本会更低。

在开发 Spring Boot 自动配置优化插件时,我就有过这样的经历。最初,我希望同时支持 Spring Boot 3.5 和 4.x。由于两个版本在架构上存在差异,我们分别实现了两套方案。后来,我意识到自己已经不再需要支持 3.5 版本,而移除这部分实现却相当麻烦。

由此得出的教训很简单:走弯路的代价很高。加速执行之前,先把需求弄清楚。

明确目标之后,我希望先制定计划。在审查计划时,我尤其关注四个方面:

  • 是否明确列出了各种假设?

  • 是否定义了验证关卡?

  • 实施方案是否符合现有架构?

  • 是否避免了不必要的工作?

第二个原则是:为不同任务定义针对性的验证关卡。

“完成”不应该意味着模型声称自己已经完成。

对于重构任务,验证关卡可以包括测试通过、确认所有预期文件均已处理、更新文档,以及确保没有遗留 TODO 项。对于幻灯片迁移,验证关卡则包括截图、视觉差异对比、动画检查、PDF 输出和内容完整性检查。

不同任务需要不同的验证关卡,但核心原则始终不变:智能体应该能够提供证据,证明工作符合预先定义的完成标准。

最近,我发现了一个名为 unlazy 的技能,它遵循类似的理念:建立明确的验证关卡,有条理地逐一完成,并证明每项要求都已满足,而不是走捷径。

第三个原则是:将经验教训转化为可复用的技能。

我开始编写并维护自己的智能体技能。每当我从一次任务执行中学到一些东西,例如智能体误解了某项约定、遗漏了必要的检查,或者我发现了更好的任务处理方式,我都会更新相应的技能。

下次执行同类任务时,这些经验就已经融入工作流程了。

例如,我有一项名为 maintainer-audit 的技能,其设计思路受到开放智能体技能社区相关实践的启发。我会在自己的各个代码仓库中使用它。它会检查并在适当情况下修复一个维护良好的项目应当具备的内容,包括仓库文档、CI 流水线、Dependabot 配置、CodeQL、安全且一致的清理流程,以及其他项目规范。

它的目标并不是再生成一份需要我阅读的审计报告,而是自动将代码仓库调整回我期望的基准状态。这让我的项目更加一致,也更容易维护。

我将这些技能视为持续演进的工程知识,而不是静态的提示词。

第四个原则是:在概率性模型周围配备确定性的工程工具。

我构建了自己的质量检查框架,将 Checkstyle、PMD 和 Spring Modulith 架构检查等工具整合在一起,生成一份智能体可以读取的综合报告。我可以通过钩子运行这些检查,让智能体不断迭代:

修改 → 执行检查 → 阅读检查结果 → 修复 → 再次执行检查。

这个过程会持续进行,直到不再发现问题。

但这并不意味着我会停止检查软件。我仍然会运行应用程序、与它进行交互,并判断它的行为和外观是否符合预期。有时,我会刻意按照功能逐个实现,因为这样可以让反馈循环更加紧密。

我逐渐认识到,仅仅因为“AI 能做到”,就要求它一次性完成所有事情,通常并不是一个好主意。宽泛而笼统的规格说明,往往会产生规模庞大、同样缺乏针对性的实现。事后再清理这些代码,可能比一开始就采用小步迭代的方式正确实现更加困难。

从更广泛的角度来看,我认为开发者应该更多地交流如何使用这些工具。分享成功的实验,也分享失败的尝试。相互借鉴并结合自身情况进行调整,而不是一味寻找适用于所有人的通用 AI 开发流程。

这也是我喜欢将 AI 视为共同探讨问题的伙伴的原因之一。周五晚上,当我想尝试某个新想法,却找不到人一起结对编程时,我突然多了一个可以与之探讨想法、审视方案,或者进行深入思考的伙伴。它就像一只非常聪明的橡皮鸭,能够帮助我梳理思路。

不过,我仍然会将这些想法带回现实中的人际交流中。人类的反馈、对产品的理解以及真实世界的背景信息,依然不可或缺。

你认为 AI 正在如何改变我们的职业?

我认为,软件工程专业的学生不应该把成为一台能够不断生产代码的机器作为主要目标,而应该努力成为优秀的问题解决者。他们需要理解用户和利益相关者,发现需求背后真正的问题,质疑未经验证的假设,理解产品和市场,权衡架构方案,保持批判性思维,以结构化的方式开展工作,并能够将一种场景中的有效解决方案迁移到另一种场景中。

模型在生成各种可能性和执行任务方面表现得极其出色。但作为人类,我通常更了解实际的背景信息,包括那些从未被记录下来的内容。

因此,我希望自己始终掌握解决方案的设计方向,同时让智能体承担越来越多的执行工作。

从这个意义上说,我认为,AI 正在让软件工程重新回归工程本身。

而我很喜欢这一点。

Frank Delporte

GitHub 主页:@FDelporte

你是如何进入科技行业的?

我 11 岁时得到了一台 Commodore 64,从那时起就对计算机着迷。毕业后,我曾短暂转向视频制作和电视行业工作,但后来又重新开始编写代码,因为当时的视频需要刻录到 DVD 上,而且最早的一批网站也开始出现了……没错,我确实已经有些年纪了 😉

GitHub Copilot 最近在哪些方面帮助了你?

它是审查拉取请求和调查 Bug 报告的绝佳助手。

有一个很有代表性的案例,涉及 Pi4J 库。这个库可以让开发者构建 Java 应用,与 Raspberry Pi 上的电子元件进行交互。2026 年 2 月,我们发布了 V4 版本,其中新增了一个使用 Foreign Function & Memory API(外部函数与内存 API)的插件。发布前,我们进行了充分测试,但发布后不久,仍有用户报告称,同时使用 5 个输入按钮时,程序无法按预期工作。按下前四个已配置按钮中的任意一个,程序都能按预期执行代码,但按下第五个按钮时,却没有任何反应。

这是我们最不希望收到的那类问题报告。因为没有任何明显的故障:没有异常、日志中没有警告,第五个按钮只是毫无反应。更糟糕的是,使用旧版 gpiod 插件时,相同的硬件和应用程序却可以正常工作。这意味着问题出在我们新实现的 FFM 代码中。这部分代码位于 Linux 内核 GPIO 字符设备之上,并通过本地调用与其通信。对于这样一部分代码,你很难指望快速找到问题的答案。

于是,我将这个问题分配给 Copilot 编码智能体,并提供了两个文件路径作为调查起点:提供程序类和 FFMDigitalInput。大约半小时后,它就创建了一个拉取请求,并给出了一个完全出乎我意料的解释。

每个输入都运行着一个事件监听器,它会阻塞在原生的 poll() 调用中,等待内核报告对应 GPIO 线路上的变化。这些监听器运行在虚拟线程中。但如果虚拟线程在原生调用中发生阻塞,就无法从其承载线程上卸载下来,而是会在整个调用期间持续占用该线程。Raspberry Pi 4 有四个 CPU 核心,因此虚拟线程调度器有四个承载线程。注册四个输入后,四个承载线程就全部阻塞在 poll() 调用中,再也无法继续执行其他任务。此时,第五个监听器虽然已经创建,却始终无法获得调度机会。这正是用户报告的问题所在。问题并不是“事件触发不稳定”,而是“第五个输入完全没有反应”。

我最欣赏的并不是代码差异,而是随后在代码审查中展开的讨论。我留下了一条评论,请 Copilot 进一步解释这个问题:

我原本以为虚拟线程的目标就是摆脱平台线程数量的限制。那么,为什么在拥有四个 CPU 核心的 Raspberry Pi 上,四个虚拟线程发生阻塞,就会导致四个线程全部被占用?

你可以查看这个拉取请求中的完整讨论。Copilot 成功地将 Raspberry Pi 的四个 CPU 核心这一硬件特征,与用户报告的问题联系起来。它不仅提出了一个简单的修复方案,还在调查过程中发现并建议了其他改进。

你能分享一些实用技巧和最佳实践吗?

  • 告诉它应该查看哪里: 在调查 Pi4J 的问题 #622 时,我提供了两个文件路径。这个小小的举动,就能减少在大型代码库中大量无谓的搜索。即使你还不知道 Bug 的具体原因,通常也会对问题可能出现在什么位置有一些直觉。把这些线索提供给工具,就不必让它从头调查整个代码库。

  • 要求它提供证据并编写测试: 如今,我经常要求它“添加一个单元测试来验证这项修改”。准确地说,我以前经常这样要求,现在已经将其变成了指令文件中的固定规则。这样也能避免错误的诊断结果被误认为正确,因为如果测试无法在旧代码上失败,就说明它可能并没有真正验证问题是否得到解决。

  • 追问原因: 如果你不理解某项修复,就直接说明并要求它解释。解释本身也是交付成果的一部分,应该补充到项目文档、问题单或拉取请求中。这样,之后阅读这些信息的每个人都能从中学习。例如,这次事件也促使 Pi4J 团队进一步讨论了原生调用中的虚拟线程固定问题。

  • 始终掌握主动权: Pi4J 中的这些拉取请求,在合并之前都经过了维护者的审查和批准。这是最适合该项目的协作模式:智能体负责调查,人类则始终对最终交付的内容负责。对于我的其他项目,我也始终会审查工具生成的代码。我希望代码始终保持可读、可维护。工具经常会创建重复的方法,或者重新实现其他位置已经存在、原本应该整合复用的功能。这些问题都需要由你来负责处理。如今,借助 AI 辅助编程,你可以非常快速地完成开发,但这也意味着你可能在很短的时间内制造出一团乱麻。不要这样做。让代码保持清晰、可管理。将来,当同事或未来的自己需要排查 Bug 或改进功能时,你会庆幸自己做出了这样的选择。

开始使用 GitHub Copilot >

Logo

微软开发者社区,邀请来自微软以及技术社区专家,带来最前沿的技术干货与实践经验。在这里,您将看到深度教程、最佳实践和创新解决方案。

更多推荐