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 DjangoTestCasefrom 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,並且精心編寫的註釋現在會進一步誤導你,讓你相信這就是你正在檢視的內容。
classIngestViewTestCase(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 thisas: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 諮詢。
你也「在看」嗎?👇

相關文章