探索生成式人工智能
类别: 生成式AI LLM 标签: 生成式AI 人工智能 LLM目录
生成式人工智能和特别是大型语言模型(LLM)已迅速进入公众意识。像许多软件开发人员一样,我对其可能性感到好奇,但不确定它最终对我们的职业意味着什么。我现在在Thoughtworks担任一个角色,协调我们关于这项技术将如何影响软件交付实践的工作。我将在这里发布各种备忘录,描述我和同事们正在学习和思考的内容。
随着智能代理编码助手变得越来越强大,反应各不相同。有些人从最近的进步推断并声称,”一年后,我们将不再需要开发人员。”其他人则对AI生成代码的质量以及为初级开发人员准备应对这一变化的挑战表示担忧。
在过去几个月中,我定期使用Cursor、Windsurf和Cline中的智能代理模式,几乎完全用于更改现有代码库(而不是从头创建井字游戏)。总体而言,我对IDE集成的最新进展以及这些集成如何极大地提升工具辅助我的方式印象深刻。它们
- 执行测试和其他开发任务,并尝试立即修复出现的错误
- 自动识别并尝试修复代码检查和编译错误
- 能够进行网络研究
- 有些甚至集成了浏览器预览功能,可以捕获控制台错误或检查DOM元素
所有这些都带来了与AI令人印象深刻的协作会话,有时帮助我在创纪录的时间内构建功能和解决问题。
然而。
即使在那些成功的会话中,我也一直在干预、纠正和引导。而且我经常决定不提交更改。在本备忘录中,我将列出具体的引导示例,以说明开发人员的经验和技能在这种”受监督的代理”模式中扮演什么角色。这些例子表明,尽管取得了令人印象深刻的进展,但我们距离AI自主编写非平凡任务的代码还有很长的路要走。它们还提供了在可预见的未来开发人员仍需应用的技能类型的想法。这些是我们必须保留和培训的技能。
我必须引导的地方
我想先说明,AI工具在我列出的事情上并不总是一定会表现糟糕。其中一些例子甚至可以通过额外的提示或自定义规则轻松缓解。缓解,但不能完全控制:LLM经常不按照提示的字面意思行事。编码会话越长,结果就越不稳定。因此,无论提示的严谨程度,或编码助手集成的上下文提供者数量如何,我列出的这些事情绝对有不可忽视的发生概率。
我将我的例子分为3种影响范围,AI的失误:
a. 减慢了我的开发速度和提交时间(与不使用辅助编码相比),而不是加快它,或 b. 为团队在该迭代中的流程创造摩擦,或 c. 对代码的长期可维护性产生负面影响。
影响范围越大,团队发现这些问题的反馈循环就越长。
影响范围:提交时间
这些是AI阻碍多于帮助的情况。这实际上是最不成问题的影响范围,因为它是最明显的失败模式,而且这些更改很可能甚至不会出现在提交中。
无法工作的代码
有时我的干预是使代码正常工作所必需的,就这么简单。所以我的经验要么是因为我可以快速纠正它出错的地方,要么是因为我很早就知道何时放弃,并启动一个新的AI会话或自己解决问题。
问题误诊
AI在误诊问题时经常陷入兔子洞。很多时候,基于我之前对这些问题的经验,我可以将工具从这些兔子洞的边缘拉回来。
例子:它假设Docker构建问题是由于该Docker构建的架构设置,并基于该假设更改了这些设置 — 而实际上,问题源于复制为错误架构构建的node_modules。由于这是我遇到过多次的典型问题,我可以快速发现并重新引导。
影响范围:迭代中的团队流程
这一类别涉及到缺乏审查和干预导致团队在交付迭代期间产生摩擦的情况。在许多交付团队工作的经验帮助我在提交前纠正这些问题,因为我已经多次遇到过这些二阶效应。我想象即使有AI,新开发人员也会通过陷入这些陷阱并从中学习来学习,就像我一样。问题是,AI增加的编码吞吐量是否会加剧这一点,使团队无法可持续地吸收。
过多的前期工作
AI通常会采取广泛而非增量实现功能工作切片的方式。这会在意识到技术选择不可行或功能需求被误解之前,冒险浪费大量前期工作。
例子:在前端技术栈迁移任务中,它试图一次转换所有UI组件,而不是从一个组件和与后端集成的垂直切片开始。
暴力修复而非根本原因分析
AI有时采用暴力方法解决问题,而不是诊断实际原因。这会将潜在问题延迟到后期阶段,并交给其他团队成员,他们必须在没有原始更改背景的情况下进行分析。
例子:在Docker构建过程中遇到内存错误时,它增加了内存设置,而不是质疑为什么使用了这么多内存。
复杂化开发人员工作流程
在一个案例中,AI生成了创建糟糕开发者体验的构建工作流程。立即推送这些更改几乎会立即影响其他团队成员的开发工作流程。
例子:引入两个命令来运行应用程序的前端和后端,而不是一个。
例子:未能确保热重载正常工作。
例子:复杂的构建设置让我和AI本身都感到困惑。
例子:处理Docker构建中的错误,而没有考虑这些错误如何能在构建过程早期被捕获。
误解或不完整的需求
有时当我没有提供功能需求的详细描述时,AI会跳到错误的结论。捕获并重定向代理不一定需要特殊的开发经验,只需要注意力。然而,这种情况经常发生在我身上,这是一个例子,说明当没有开发人员监视并在开始而不是结束时干预时,完全自主的代理可能会失败。无论是不思考的开发人员,还是完全自主的代理,这种误解都会在故事生命周期的后期被发现,并且会导致大量来回修正工作。
影响范围:长期可维护性
这是最隐蔽的影响范围,因为它有最长的反馈循环,这些问题可能要到几周甚至几个月后才会被发现。这些是代码现在可以正常工作,但将来更难更改的情况。不幸的是,这也是我20多年编程经验最重要的类别。
冗长和冗余的测试
虽然AI在生成测试方面可能非常出色,但我经常发现它创建新的测试函数,而不是向现有函数添加断言,或者它添加了太多断言,即一些已经在其他测试中涵盖的断言。对于经验较少的程序员来说,这可能有悖直觉,更多的测试不一定更好。测试和断言被重复得越多,维护它们就越困难,测试就越脆弱。这可能导致一种状态,即每当开发人员更改部分代码时,多个测试都会失败,导致更多开销和挫折感。我尝试通过自定义指令来缓解这种行为,但它仍然经常发生。
缺乏重用
AI生成的代码有时缺乏模块化,使得难以在应用程序的其他地方应用相同的方法。
例子:没有意识到某个UI组件已在其他地方实现,因此创建了重复代码。
例子:使用内联CSS样式而不是CSS类和变量
过于复杂或冗长的代码
有时AI生成太多代码,需要我手动删除不必要的元素。这可能是技术上不必要并使代码更复杂的代码,这将导致将来更改代码时出现问题。或者它可能比我在那一刻实际需要的功能更多,这可能会增加不必要代码行的维护成本。
例子:每次AI为我做CSS更改时,我都会逐一删除有时是大量冗余的CSS样式。
例子:AI生成了一个新的Web组件,可以动态显示JSON对象内的数据,它构建了一个非常复杂的版本,这在当时并不需要。
例子:在重构过程中,它未能识别现有的依赖注入链,引入了不必要的额外参数,使设计更脆弱,更难理解。例如,它向服务构造函数引入了一个不必要的新参数,因为提供该值的依赖已经被注入。(value = service_a.get_value(); ServiceB(service_a, value=value))
结论
这些经验意味着,按照我个人的想象,我们不会在一年内拥有可以自主编写90%代码的AI。它会辅助编写90%的代码吗?也许吧。对于某些团队和某些代码库。它今天在80%的情况下辅助我(在一个中等复杂、相对较小的15K行代码库中)。
如何防范AI失误?
那么,如何保护您的软件和团队免受LLM支持的工具的反复无常影响,以利用AI编码助手的好处?
个人编码者
始终仔细检查AI生成的代码。我几乎没有找不到需要修复或改进的地方的情况。
当您感到被正在发生的事情所淹没时,停止AI编码会话。要么修改提示并开始新会话,要么回到手动实现 - 正如我的同事Steve Upton所说的”手工编码”。
对在很短时间内奇迹般创建的”足够好”的解决方案保持谨慎,但它们会带来长期维护成本。
练习结对编程。四只眼睛比两只眼睛发现得更多,两个大脑比一个大脑不那么自满
团队和组织
好的代码质量监控。如果你还没有,设置像Sonarqube或Codescene这样的工具来提醒你代码气味。虽然它们不能捕获所有内容,但它是安全网的良好组成部分。一些代码气味在使用AI工具时变得更为突出,应该比以前更密切监控,例如代码重复。
预提交钩子和IDE集成代码审查。记住尽可能地左移 - 有许多工具在拉取请求或管道中审查、检查和安全检查您的代码。但是在开发过程中直接捕获的越多越好。
重新审视良好的代码质量实践。鉴于这里描述的陷阱类型,以及团队经历的其他陷阱,创建仪式来重申缓解外部两个影响范围的实践。例如,您可以保留一个”出错”日志,记录AI生成的代码导致团队摩擦或影响可维护性的事件,并每周反思一次。
利用自定义规则。大多数编码助手现在支持配置规则集或指令,这些规则集或指令将随每个提示一起发送。作为一个团队,您可以利用这些来迭代提示指令的基线,以编纂您的良好实践并缓解这里列出的一些失误。然而,正如开始时提到的,AI是否会遵循它们并不保证。会话越大,因此上下文窗口越大,结果就越不稳定。
信任和开放沟通的文化。我们正处于一个过渡阶段,这种技术正在严重扰乱我们的工作方式,每个人都是初学者和学习者。具有信任文化和开放沟通的团队和组织更有能力学习和处理由此产生的脆弱性。例如,一个对团队施加高压力以”因为你现在有AI”而更快交付的组织更容易暴露于这里提到的质量风险,因为开发人员可能会为了满足期望而走捷径。在高度信任和心理安全的团队中的开发人员会发现更容易分享他们在AI采用方面的挑战,并帮助团队更快地学习以从工具中获得最大收益。