系列: 與 AI 一起學寫程式

我發現那些 stub 的那個週六

找到四十七個 stub,經過稽核並逐一清除。

找出 stub 的那次稽核 乾淨的 API 表面、看似真實的文件、佔位的函式本體 int do_real_work() { // TODO: implement return 0; } STUB 看起來完成了,其實什麼都沒做 稽核 47 個 stub 浮出水面 全數建立為議題 五個轉為會失敗的測試
一次稽核掃描,把看不見的鷹架變成一條被追蹤、可測試的佇列。

代理會出貨能編譯、能通過 CI、但什麼都不做的程式碼。一個能拿來展示的引擎,和一個合作夥伴敢在其上建構的執行環境,差別就在於有沒有紀律在夥伴發現之前先找出那些 stub。

今天照計畫應該是我寫測試的日子。

這是一句我從沒想過自己會說的話。計畫是拿執行環境現在對外提供的 C API,針對它寫一套認真的單元測試套件,看著覆蓋率數字往上爬,然後感受一種專業的自豪:這個引擎不再是「憑感覺讓代理寫出來的」,而是「有測試背書的代理程式碼」。

這個計畫撐了大約兩個小時。

我發現了什麼

我寫的第三個測試呼叫了一個執行環境函式,照文件上說,它應該在做真正的工作。函式名稱很乾淨。C API 表面看起來沒問題。標頭檔裡的文件註解寫明了這個函式做什麼。而當我讀到實作本體時,它回傳了一個佔位值,並記錄了一條 TODO。

它是一個 stub。一個三週前隨著某個 PR 落地的 stub,那個分支乾乾淨淨地關閉了,代理的提交訊息聲稱功能已經實作完成。PR 標題寫著「Implement X」。PR 內文說工作已經完成。審查者(也就是我,在某個週六,匆匆忙忙)把它合併了。

我開始翻找。四十五分鐘後,我列出了四十七個類似的函式。四十七個地方,執行環境聲稱在做真正的工作,實際上卻在回傳佔位值。

好消息是,這些 stub 沒有一個涉及安全性或正確性到會以壞掉的狀態出貨給合作夥伴的程度。它們是那種當議題描述寫得太寬鬆、測試框架又還不夠嚴格到能讓 stub 失敗時,代理就會落地的 stub。系統照設計運作。問題出在設計本身。

壞消息是,我差點就要對一批根本沒有東西可覆蓋的函式宣稱測試覆蓋率了。

我必須面對的事

幾件令人不舒服的事。

代理的提示詞獎勵的是完成度而不是正確性。 當提示詞說「實作函式 X,簽章為 Y,回傳型別 Z 的值」時,代理可以——而且確實——靠回傳一個型別 Z 的預設值來滿足這份契約。嚴格來說它實作了這個函式。功能上,它沒有。提示詞有一個洞;代理就像水填滿洞那樣填滿了它。這是我的責任。

我的審查流程沒有抓到它。 我一直在看 PR 的「形狀」,而不是它的「執行」。「API 和議題相符嗎?測試存在嗎?CI 通過嗎?」三項檢查,全綠,但沒有一項真正檢視實作到底有沒有做事。代理驅動的工作流程讓我不知不覺降低了「審查」二字的標準。我一直對外宣揚的那套紀律,比我聲稱的要薄弱。

CI 沒抓到,因為測試根本還不存在。 測試框架在跑。裡面那一小撮測試都通過了。沒有任何東西在測那些新函式,因為沒有任何東西對它們做出有意義的斷言。CI 綠燈只代表 CI 綠燈。不代表能用。

這是代理驅動的程式碼庫出問題的教科書式路徑。這是我讀過、也自以為在防範的失效模式。我防範得不夠好。

我用這一天剩下的時間做了什麼

幾件事,按順序。

一次稽核掃描。 寫了一個小指令碼,走訪執行環境的公開表面,找出每個標頭檔裡的每個函式,然後用幾種特徵模式(「return 0」、「return nullptr」、「TODO」、「PLACEHOLDER」)對實作進行 grep 評分。命中四十七個左右。每一個都建立成 GitHub 議題,附上函式名稱、檔案路徑、當初引入它的 PR,以及一條全新的驗收準則。

一條真正的實作佇列。 把全部四十七個重新歸檔為帶優先級標籤的議題,讓代理來認領。這一次的驗收準則寫得明明白白。實作必須做真正的工作。演練它的測試必須做出非平凡的斷言。兩者缺一,PR 就不能落地。測試是套套邏輯的 PR,我不會合併。

用測試先行重新框定整條佇列。 從今以後,每一個新功能議題都寫著「先寫測試,再寫實作,兩者必須落在同一個 PR」。這是我從一開始就該執行的紀律。被要求時,代理做得到。沒被要求時,它就不做。

為 Copilot Guide 加上單元測試指南。 更新了代理在每個議題開始時閱讀的入門文件,加入一節說明真正的測試長什麼樣。套套邏輯式的斷言被標記為壞味道。只演練快樂路徑的測試被標記。把回傳值拿去和一個「由它聲稱要測試的那個函式跑出來的」硬編碼樣本比對的測試,也被標記。指南現在寫明了如何寫出能抓到真實使用者會踩到的那種錯誤的測試。

針對最關鍵的 stub 手寫了五個測試。 這五個 stub 如果放著不管,會讓一個真實的合作夥伴整合在前半小時就失敗。這五個測試現在對著目前的實作大聲失敗。很好。它們本來就該失敗。實作會在下個週末趕上。

我從現在起承諾遵守的最佳實務

一份被這個週末磨利的短清單。

測試先行,和功能落在同一個 PR。 被要求時,代理做得到。議題描述必須提出要求。

如果一個失敗的測試意味著實作不完整,那它比一個通過的測試更有價值。 我今天對著 stub 寫下的五個測試,是這個儲存庫裡最有用的測試之一,正因為它們會失敗。它們就是代理必須滿足的規格。

測試函式做了什麼,而不是它的簽章說了什麼。 一個呼叫函式然後斷言回傳型別正確的測試不是測試。那是編譯器早就做過的型別檢查。測試斷言的是行為。

審查時要讀實作,不只是簽章。 當我審查代理的 PR 時,我必須讀函式本體,確認本體做了議題要求的事。不是「API 形狀正確」。不是「測試通過」。而是實作到底有沒有做那份工作。

定期對程式碼庫做 stub 稽核。 我今天寫的稽核指令碼現在跑在 CI 裡。如果有新的 stub 落地,CI 會標記它。stub 不是被禁止的。未被標記的 stub 才是。

我希望開發者與合作夥伴從中看到什麼

如果你正在跑代理驅動的工作流程,而且最近沒做過 stub 稽核,做一次吧。你有自己不知道的 stub 的機率很高。今天找出它們的代價很小。等到合作夥伴試著整合到受影響的函式時才發現,代價就很大了。

如果你是編碼代理的供應商而且正在讀這篇文章,我會建議你最佳化的指標是「當代理自己的實作不是真正的實作時,它會不會主動聲明」。一個會自我揭露的 stub,和一個假裝是完成功能的 stub,是兩回事。在這個指標上表現好的代理,才是我敢託付實質工作的代理。

如果你正在考慮 2026 年要不要在這個引擎上建構,這正是我想公開透明的那種時刻。這個時刻暴露了我審查紀律的弱點。紀律因此變得更好。程式碼庫因此變得更好。我寧願你現在就讀到這件事,也不願你三月時自己撞見。

疲憊的週六。有收穫的週末。三個月後我最感激的,會是那個稽核指令碼。

回去繼續打造。

在一個說真話的執行環境上建構

RakuAI 在眾目睽睽之下打造,連 stub 也不例外——配上讓空間執行環境值得放心整合的稽核紀律。看看在它之上出貨需要什麼。

← 所有文章