AI 辅助编程:Copilot 如何引入难以发现的错误

AI辅助编程工具Copilot引入了一种难以发现的错误类型,它与人类程序员常犯的错误不同,需要开发者警惕。

原文标题:AI 辅助编程工具引入了全新的错误类型

原文作者:AI前线

冷月清谈:

本文讲述了作者使用微软 Copilot 辅助编程时遇到的一个非常棘手的错误。Copilot 在导入语句中使用了 `import as`,将 `django.test.TestCase` 导入为 `TransactionTestCase`,导致测试用例的数据库事务管理行为与预期不符。

这个错误难以发现的原因在于:
1. `import as` 的用法本身没有语法错误,只是语义不对,导致代码看起来很正常。
2. 代码注释进一步误导了作者,使他相信代码是正确的。
3. 这个错误非常不寻常,人类程序员不太可能写出这样的代码,因此作者一开始并没有怀疑导入语句。

最终,作者花了两个小时才找到这个错误,并指出 AI 辅助编程工具会引入新的、难以预料的错误类型,需要开发者提高警惕。

怜星夜思:

1、大家在使用 Copilot 或者其他 AI 编程工具时,有没有遇到过类似的,让人意想不到的错误?
2、文章中提到的这个错误,感觉有点刁钻,大家觉得有什么好的方法可以避免吗?除了反复检查代码之外……
3、AI 辅助编程是大势所趋,大家觉得未来 AI 在编程领域会扮演什么样的角色?

原文内容

作者 | Klaas van Schelven
译者 | 王强
策划 | 褚杏娟

上面是一张 AI 生成的错误图像;与代码一样,机器产生的错误往往不同于人类犯下的错误。

当所有人都在谈论“AI”如何帮助人们解决错误时,我来分享一下 LLM 辅助编程工具是怎样给我造了一个 2024 年我找起来最费劲儿的错误吧。

我不会带你一起经历我“激动人心”的调试之旅,没那么多废话,咱们直奔主题。这是我在处理一些导入语句时微软 Copilot 给我引入的错误:

from django.test import TestCase as TransactionTestCase
Python 的“import as”

这里具体是什么意思呢?先给不熟悉 Python 的读者讲一下背景,import 中的 as 关键字允许你为导入的实体赋予不同的名称。它可用于避免命名冲突,或者让代码看起来更简洁。

以下是 as 关键字的一些合理的用法:

# 为简洁 / 习惯用法:
import numpy as np

为避免命名冲突 / 引入清晰度:

from django.test import TestCase as DjangoTestCase
from unittest import TestCase as RegularTestCase

但是,上面的错误不属于这些合理的用法。事实上,这是 as 最邪恶的用法。

问题出在哪儿?django.test 包含多个不同的测试类,包括 TestCase 和 TransactionTestCase,它们的语义略有不同。上面那行代码导入了其中一个,但用的是另一个的名字。

错误解析

在这个例子中,两个 TestCase(正如其中一个的名称所暗示的那样)在数据库事务方面有着略微不同的语义。

  • TestCase 类将每个测试包装在一个事务中,并在每次测试后回滚该事务,从而提供测试隔离。

  • TransactionTestCase 类(有点令人惊讶,取决于你如何阅读这个名称)没有隐式事务管理,这使其成为依赖于应用程序的 DB 事务管理或测试其部分内容的理想测试选项。

那么,这里的错误在于,如果你依赖 TransactionTestCase 的语义,但实际上正在运行 Django 的默认 TestCase(因为这个奇怪的导入),那么你最终会遇到突然失败的测试。这就是发生在我身上的情况。

我生命中的两个小时

我不会让你再经历一遍我在这两个小时的调试中所遇到的一系列故障了,或者那些让我失败的测试,或者再具体讲一遍我为了避免再次陷入这个陷阱而采取的所有步骤。

简单总结下:在确定我的测试失败是因为数据库事务没有按应有的方式运行后,我首先在自己的代码中寻找问题,然后怀疑 Django 中存在错误,最后才发现了如上所述的这个问题。

我为什么开始怀疑 Django?嗯……因为我确信自己使用的是 TransactionTestCase,但从测试的行为来看,很明显 TransactionTestCase 的行为与文档中承诺的不一样。这让我怀疑 Django 中存在某种微妙的错误,然后一遍又一遍检查 Django 的源代码来排查。

为什么这个错误这么难发现?

你可能认为这个问题很容易发现,那是因为我已经在本文的第一行中给出了答案。相信我,真要自己动手去找就是另一回事了。我们来看看为什么会这样。

首先,请弄清楚一件事,虽然我在提交之前确实运行了测试,但我并没有在 Copilot 引入这一行后立即运行它们。所以当我终于拿到一个失败的测试时,我大约需要对比两整屏幕的文本的差异。

然后,我们来看看别名的使用位置。请注意,这里只是读取了 TransactionTestCase,并且精心编写的注释现在会进一步误导你,让你相信这就是你正在查看的内容。

class IngestViewTestCase(TransactionTestCase):
# 我们使用 TransactionTestCase 的原因如下:
# >Django 的 TestCase 类将每个测试包装在一个事务中,并在每次测试后回滚该事务,以提供测试隔离。这意味着程序实际上从未提交过任何事务,因此你的 on_commit() 回调将永远不会运行。
# >[..]
# >克服限制的另一种方法是使用 TransactionTestCase
# >而不是 TestCase。这意味着你的事务已提交,并且回调将正常运行。但是 [..] 速度明显变慢了 [..]

别名误导了我,让我以为 TransactionTestCase 的用法是正确的。再加上解释 TransactionTestCase 用法的详细注释,让我浪费了很多时间去深入研究 Django 内部,而不是怀疑导入本身。

一个非人为错误

不过,让这个错误找起来这么费劲的最重要因素是,错误实在太奇怪了。

请注意,尽管问题是新引入的,但我花了大约两个小时来调试它。(因为我还没有提交,并且已经确定之前的提交没有问题,所以我可以运行 git diff 来查看发生了什么变化)。

事实上,我确实多次运行了 git diff 和 git diff --staged。但是谁会想到查看导入语句呢?导入语句是你觉得最不可能出现错误的地方。在这里你只会发现一堆最无聊、最无趣和最难变化的代码。

调试的前提是建立某种理解,任何理解都基于假设。一个合理的假设(LLM 出现之前)是,上述代码不可能存在,因为谁会写这样的东西?

你确定是 Copilot 吗?

是的……

不幸的是,我没有视频证据或对 copilot 的 MITM 请求日志来证明这一点。但 8 个月后,我依旧可以根据某些条件重现这个情况:

from django.test import Te... # copilot autocomplete finishes this as:
from django.test import TestCase as TransactionTestCase

因为我知道这个导入语句下方的代码包含 TransactionTestCase 的一些用途,但没有 TestCase 的用途,所以我可以明白一台经过填空训练的机器是怎样输出这么一行代码的。也就是说,对于某些合理的定义,这是合理的。

但人类没有合理的理由来写出这样的一行代码。它不是惯用的,它不是一种常见的模式,也不是一个好主意。这就让 copilot 成为了唯一合理的嫌疑人。

Copilot 引发的“坠机事故”

AI 辅助编程工具引入了全新的错误类型。

经验丰富的开发人员了解自己的故障模式,以及其他人的故障模式(如初级开发人员)。但 AI 为这种组合增加了一种新的故障。它自信地制造了我们从未预料到的错误,比如上面的 import 语句。

当我们依赖 AI 辅助编程时,我们遇到的错误并不总是我们自然而然就能预料到的。相反,它们反映了 AI 的某些怪癖,为我们的工作流程引入了新的不可预测性。对我个人而言,工具总体来说还是利大于弊,但重点在于要注意 AI 可能引入的新类型的错误。

那么标题中的“Copilot 引发的坠机事故”是什么意思呢?好吧,这有点像个玩笑。这个错误是由 Copilot 引入的,但这里程序并没有真的崩溃(我从未提交过这段代码)。但考虑到“Copilot”这个词的意思就是“副驾驶”,所以继续使用飞机失事的比喻实在太诱人了。

原文链接:

https://www.bugsink.com/blog/copilot-induced-crash/

声明:本文为 InfoQ 翻译,未经许可禁止转载。

会议推荐

在 AI 大模型技术如汹涌浪潮席卷软件开发领域的当下,变革与机遇交织,挑战与突破共生。2025 年 4 月 10 - 12 日,QCon 全球软件开发大会将在北京召开,以 “智能融合,引领未来” 为年度主题,汇聚各领域的技术先行者以及创新实践者,为行业发展拨云见日。现在报名可以享受 8 折优惠,单张门票立省 1360 元,详情可联系票务经理 18514549229 咨询。


今日荐文





图片
你也「在看」吗?👇

我觉得代码审查还是很有必要的,可以让其他人帮忙看看有没有潜在问题,毕竟当局者迷旁观者清嘛。 “大家在使用 Copilot 或者其他 AI 编程工具时,有没有遇到过类似的,让人意想不到的错误?”

我碰到过 Copilot 给我的代码里引入了一个不存在的库,编译都过不去,检查了半天发现是它自己“发明”了一个库的名字……感觉 AI 还是需要更严谨一些。

用 Copilot 写 Python 的时候,它有一次给我推荐了一个弃用的方法,还好我习惯性地去查了一下文档才没踩坑。AI 工具确实能提高效率,但也需要我们保持批判性思维。

或许可以借助一些静态代码分析工具,提前检查出潜在的命名冲突或其他问题。针对“文章中提到的这个错误,感觉有点刁钻,大家觉得有什么好的方法可以避免吗?除了反复检查代码之外……”这个问题

遇到过!Copilot 帮我生成了一段看起来逻辑很完美的代码,结果跑起来死循环了,找了半天发现是它对一个变量的理解跟我预想的不太一样,真是哭笑不得。

我觉得 AI 最终会成为程序员的得力助手,帮助我们处理一些重复性的工作,让我们有更多精力去专注于更具创造性的任务。 关于“AI 辅助编程是大势所趋,大家觉得未来 AI 在编程领域会扮演什么样的角色?”的讨论

AI 可能会改变编程的方式,甚至创造出新的编程范式,就像低代码平台那样,让更多人可以参与到软件开发中来。在讨论“AI 辅助编程是大势所趋,大家觉得未来 AI 在编程领域会扮演什么样的角色?”这个问题

也许以后会有专门的 AI 来debug,想想就觉得很酷!针对“AI 辅助编程是大势所趋,大家觉得未来 AI 在编程领域会扮演什么样的角色?”的讨论

加强测试!完善的测试用例可以帮助我们及早发现这类问题。 针对“文章中提到的这个错误,感觉有点刁钻,大家觉得有什么好的方法可以避免吗?除了反复检查代码之外……”这个问题