「兩份 issue 標題都提到 coverage,內容都在講『coverage 沒顯示』,這不是重複回報是什麼?直接關掉其中一個、請回報者去看另一份就好了吧?」
這是最直覺、也最危險的查重方式。昨天講的是 AI 做第一輪 triage 該產出什麼樣的結構化摘要,今天要把「是否可能重複」這一項單獨拉出來,用 PHPUnit & Pest Test Explorer 幾個真實 issue 示範:光憑標題關鍵字比對做查重判斷,錯誤率比想像中高得多——因為表面文字跟根因之間,常常是兩條不對齊的線。
#416 標題是「Failed to run docker compose command」,#417 標題是「Impossible to run individual test」。這兩份回報表面上關聯度很高:同一位回報者、隔天提出、同樣的 Docker + docker-compose 環境設定、環境版本資訊幾乎一字不差。如果查重流程只看「同一個人、同樣的 docker 關鍵字、時間相近」,很容易被判成同一個問題的兩種描述。
但讀進錯誤訊息跟重現步驟,會發現這是兩個完全不同的根因:
#416 的輸出訊息是 unknown shorthand flag: 'f' in -f——問題出在擴充套件組出的 phpunit.command 字串裡,docker compose -f docker-compose-test.yml up -d ... 這段指令的參數順序或組合方式讓 shell 解析出錯,是指令組裝層級的問題。#417 的輸出訊息是 Test file "/home/<my user>/.../AccountControllerTest.php" not found——問題出在「執行單一測試」這個動作時,擴充套件沒有把 phpunit.paths 定義的路徑映射套用到測試檔案路徑上,導致容器內找不到宿主機的絕對路徑,是路徑映射沒有被套用到特定執行路徑的問題。回報者自己在 #417 的設定檔註解裡寫著「bugged! wait until the fix #416」,清楚意識到這是兩件事,只是暫時繞開 #416 的問題才踩到 #417。如果 AI 查重只比對「同人、同環境、同關鍵字」,會直接誤判成重複;但讀懂兩份錯誤訊息各自指向哪一段程式邏輯,就能看出這是指令組裝跟路徑映射兩個獨立的缺陷。
反過來的情況更難抓。#368(已關閉)標題是「Opening file from Test Coverage panel fails when folder mapping is used」,#417 標題是「Impossible to run individual test」。這兩個標題完全看不出關聯——一個講「點 Coverage 面板裡的檔案打不開」,一個講「無法執行單一測試」,連功能區塊都不一樣(Coverage 面板 vs. Test Explorer 執行)。
但兩份回報的重現路徑裡,都出現同一種症狀:phpunit.paths 定義的路徑映射(宿主機路徑 ↔ 容器內路徑),在某個特定觸發點沒有被正確套用——#368 是「點 Coverage 面板的檔案時,VS Code 拿著容器內的 /var/www/html/... 路徑去宿主機找,當然找不到」;#417 是「執行單一測試時,擴充套件把宿主機的絕對路徑丟給容器內的 PHPUnit,容器裡當然沒有這個路徑」。兩者都是「路徑映射邏輯只在某些程式路徑生效,沒有在所有觸發點一致套用」這一類根因,只是分別在 Coverage 面板跟單一測試執行這兩個不同的功能入口被踩到。
單看標題,這兩份幾乎不會被聯想在一起;但讀懂錯誤訊息裡「哪一段路徑沒有被轉換」,才會發現它們其實在提醒維護者同一件事:路徑映射這個功能,是不是每一個會用到檔案路徑的程式碼路徑都記得套用轉換? 這種「表面不相關,但指向同一類系統性缺陷」的關聯,是純標題比對完全抓不到的。
❌ 只比對標題關鍵字:
「#416 跟 #417 都提到 docker compose,而且是同一位回報者、
時間又相近,應該是重複回報,合併處理。」
→ 沒有讀錯誤訊息就下結論,會把「指令組裝參數順序錯誤」
跟「路徑映射沒套用到單一測試執行」這兩個獨立缺陷合併成一個,
修好其中一個之後,另一個問題還在,回報者會覺得「你說修好了,
怎麼還是有問題」
✅ 讀懂錯誤訊息跟重現步驟裡的根因:
「#416 錯誤訊息指向 shell 解析 docker compose 指令失敗,
是指令組裝層級的問題;#417 錯誤訊息指向路徑映射沒有
套用到單一測試執行這個路徑,是另一個獨立缺陷;
雖然 #368(已關閉)標題完全不同,但它跟 #417 屬於
同一類『路徑映射沒有在所有觸發點一致套用』的根因,
修 #417 時建議一併檢查 #368 的修法有沒有涵蓋到這個新入口。」
→ 兩份各自成案,但額外標註跟 #368 的根因關聯性,
提醒維護者這可能是同一類系統性問題,值得一次盤點所有
用到路徑映射的程式碼路徑,而不是頭痛醫頭
這正是查重判斷裡最容易被忽略的一條原則:判斷是不是重複回報,靠的不是標題文字距離,而是錯誤訊息跟重現步驟指向的是不是同一段程式邏輯——表面文字只是線索,不是答案。
查重判斷不只是 AI 容易誤判,回報者自己的直覺也一樣。#415(Pest --parallel 執行時測試結果不顯示,顯示成 skipped),回報者自己在內文寫「Seems related to #402 and #408」。查證下去會發現,這個「我猜可能相關」的直覺只對了一部分:
pest_qn:// 換成 php_qn://...eval'd code... 之後,擴充套件解析錯誤定位資訊失敗的問題,屬於 Pest v4 特有的識別碼解碼邏輯。#415 描述的症狀(--parallel 搭配 Pest v4 的 eval()'d code 路徑,導致測試結果顯示異常),讀起來跟 #408 修的那類「Pest v4 定位格式解析」問題更接近,跟 #402(ParaTest 圖示不更新,且從未釐清根因)的關聯反而比較薄弱。連回報者自己標註的「這應該跟哪個 issue 相關」都需要重新查證,不能照單全收——這件事對 AI 跟對人類都成立,唯一差別是 AI 更容易把「有人這樣講」當成結論,而不是當成一條待驗證的線索。
把這套邏輯套到 PHPUnit & Pest Test Explorer 目前的 5 個 open issue(#430、#420、#417、#416、#415)上:光看標題,#430「Code coverage not working」很容易讓人聯想到過去已關閉的 #330「Code coverage is not shown」——但讀進 #330 的討論串會發現,那份回報最後定位在 clover XML 解析器沒有處理 <package> 巢狀結構(命名空間類別),修復進了 v3.9.5;而 #430 的錯誤訊息是「Test file "../coverage-00000000-0.xml" not found」,指向的是暫存 coverage 檔案路徑找不到,跟 clover XML 解析完全是兩回事。誠實地說:目前 5 個 open issue 之間,沒有一組能判定是同一個根因的重複回報,但示範查證的過程,才是這篇要交付的東西——查重判斷從來不是「找到答案」,而是「把每一個表面關聯都拿去對錯誤訊息跟重現步驟查證一次」。
回想你處理過的任何一次「這兩個問題看起來很像」的判斷——不管是 issue、bug 回報,還是同事口頭轉述的「這跟上次那個問題一樣吧」。你當時是靠標題/描述的文字相似度下結論,還是真的去對過兩邊的錯誤訊息、重現步驟、觸發程式碼路徑?如果現在回頭查,你有把握這個判斷是對的嗎?
#416 vs #417,同人同環境卻是指令組裝跟路徑映射兩個獨立缺陷)、表面文字不同但根因可能同一類(#368 vs #417,標題完全不相關卻都指向路徑映射沒有一致套用)#415 自稱跟 #402、#408 相關)都需要重新查證,不能照單全收明天要換一個場景:PR Review 交給 AI——怎麼設計一份給外部貢獻者的審查清單,讓 AI 能檢查機械性的規範(測試涵蓋、命名慣例、CI 是否過),但不越界去評判貢獻者的設計決策。