我曾説我對測試一無所知,但我的倉庫裏有342個測試
一位沒有編程背景的“氛圍編碼者”在AI輔助下完成項目後,驚訝地發現AI自動編寫了342個自動化測試。文章深入探討了技術債務、手工測試的盲點,以及測試對於非程序員批量運行AI代碼的關鍵作用。
最近一則評論認為,AI寫的代碼都是垃圾,任何基於AI構建的產品只能停留在MVP階段,無法真正交付。我正是這類評論所指的人——沒有工程背景,純“氛圍編碼者”(vibe coder),每行代碼都由AI編寫,我自己完全看不懂代碼。我當時的反應是:只要程序上線後能正常工作,管它代碼乾不乾淨?
但後來發生兩件事:第一,我與人深入討論了“意大利麪條式代碼”的真正含義,直到這個詞的含義徹底印在我腦海裏。第二,我回看剛完成項目的倉庫,發現裏面竟然有342個自動化測試。而就在幾天前,我還親口説過“我對測試完全沒有概念”。本文就是我對這兩件事的反思。如果你是靠AI寫代碼且自己不會讀代碼的人,這篇文章可能是你最快理解那個常被提起的術語的途徑。
1. 什麼是“意大利麪條式代碼”
意大利麪條式代碼——也叫遺留腐化、技術債務——是指多年積累下來、沒人敢碰的代碼。關鍵是“積累”二字。它不是一次寫壞的,而是逐層疊加:第一個人為了趕工期寫了一堆粗糙但能跑的代碼;第二個人需要加功能,看不懂之前的邏輯,不敢改動,於是在上面再糊一層;第三個人修bug,發現改A會破壞B,於是不治本只治標地打個補丁。幾年後,沒人能理解整座代碼山。
反直覺的是,這種代碼通常運行得很好。地球上一些最關鍵的系統——銀行、航班預訂——就在這樣的代碼上運行。正因為運行正常且無人理解,“別動它”成為最理性的選擇。
因此,壞代碼的成本從來不在運行它,而在改動它。今天一個混亂的產品可以順利上線、運行良好。問題出現在三個月後你想加一個新功能時:改A導致B壞,修B又搞垮C,每次修改的成本越來越高,直到重寫比修復更便宜。壞代碼不是質量問題,而是債務。揹債時沒感覺,還債時連本帶利一次付清。
理解了這一點,就能看清我之前“能用就行”的思維錯在哪裏:對方問的是“能不能改”,我用“能不能跑”來回答,完全答非所問。但我的直覺也不全錯。AI時代新增了一個變量:重寫的成本暴跌。過去重建一個系統需要數月,現在可能一個下午就能重新生成。對單人加AI的項目來説,代碼開始像一次性叉子——用完就扔,需要時再造。糾結於“代碼是否乾淨”沒有意義。從這個角度看,用大公司團隊協作的標準衡量單人AI項目,是在打上一場戰爭。
有一個例外會打破這種安逸:當項目大到AI無法一次性“記住”時,AI也會在自己的混亂中迷失——讓它修一個bug,它引入三個。這時你既不能讀代碼自己修復,AI也理不清頭緒。項目就真的死了。這才是氛圍編碼者的真正門檻,不是“能否上線”。
2. 我其實已經吃過一次虧,只是當時沒意識到
我剛剛完成的項目是一個自動化工具,用於發現網紅並管理投放活動。兩個月,一百多次提交,從一個小爬蟲腳本成長為一個完整管道,最終部署在公網上。
期間出過一次數據事故:一批網紅ID被破壞——數據錯了,但程序仍然正常運行併產生輸出。我當時想,這很簡單:既然已經定位到ID在頁面結構中的位置,剩下的只是提取出來?
但這個“只是提取”變成了一場消耗戰。我使用的AI不斷編寫診斷工具,每次換一種邏輯——倉庫裏至今還躺着四個這樣的工具,成為那場戰鬥的遺蹟。我一度氣得想讓它清空整個數據庫從頭爬取,只想快點結束。最後我換了一個不同的AI才找到根因。事後查閲文檔,我發現更可怕的事:其中一個修復工具自身有個bug,差點用錯誤值覆蓋了正確數據。救火的差點放了火。
當時我只覺得煩。現在回顧,整個過程就是一次小規模混亂的加速版:每個新診斷工具都是在之前混亂之上再堆一層。AI陷入自己的沉沒成本,在錯誤路徑上越挖越深,無法脱身。而“換個AI就解決了”本身也很有啓發——新AI不帶任何包袱,一眼看到根因。這正是人類團隊的工作方式:有時解開亂局的人不是最聰明的,而是沒有歷史需要辯護的人。
其中有一個更大的教訓我那時也沒意識到:真正拖垮我的不是bug本身,而是每輪診斷都需要一個不會讀代碼的人做決定——我必須不斷對我根本不理解的細節拍板。記住這種感覺,後面還會用到。
3. 然後我發現了那342個測試
在關於“意大利麪條”的對話結束後幾天,我重新打開那個項目的倉庫。45個測試文件,342個自動化測試。文檔裏寫着“全部測試通過”。而就在幾天前,我還説過測試是我“完全沒有概念”的東西。
不僅僅是測試。文檔裏還有一些非常成熟的安全決策:攝入表有三個僅限內部的列,旁邊寫着“絕不要暴露給公共表單,否則公共用户可能觸發內部工作流”;遠程控制表通過嚴格白名單解析命令,絕不執行任意指令。所有這些防禦性的思考——沒有一樣是我的。全是AI做的,無需提示。
有人問我:你當時知道AI在寫這些嗎?老實説,我知道它在寫一些叫“測試”的東西,但我沒有真實感受。我不知道這些測試在防禦什麼,也不知道它們已經抓到了什麼。
現在我知道了。我的項目運行了兩個月沒有崩潰,最終順利上線。我過去以為這歸功於我的流程——先詳細説明每個功能,再寫計劃,然後構建。這確實是原因之一。但另一部分是AI默默地支付了一筆我不知道存在的税款。那342個測試就像腐敗的煙霧報警器:每次改動後,它們重新驗證所有舊功能,一旦“改A破壞了B”,測試就會變紅。我從未見過報警器響——不是因為沒危險,而是因為每次報警都被AI自己聽到並處理了。
這是我首先想反駁那些評論的話:説“AI代碼全是垃圾”的人可能低估了一件事。AI不只是堆積混亂,它同時也在默默地做防範混亂的工程工作——比它的用户所知道的要多得多。
4. 測試到底防範什麼:手工QA的兩個盲點
在發現那些測試之前,我驗收功能的標準只有一個:自己試用。如果它能運行併產生輸出,就算通過。AI説“完成”,我就認為模塊完成了。
這是人工QA,很多小團隊確實這麼做——這沒什麼可羞愧的。但它有兩個盲點,而我恰好都踩了進去:
盲點一:你只測試你點擊的東西,而混亂正是發生在你沒點擊的地方。“改A壞B”的麻煩之處在於,你改了A,自然回去驗證A,但被破壞的B是上個月的功能,你根本不會想起去重新檢查。項目小時,“用一次”差不多是全量覆蓋,這個前提成立。但當你擁有幾十個功能時,每次只覆蓋最新部分,其餘功能無人看管。我算了下自己的項目:從頭手動點擊所有功能至少需要半小時——這意味着全量人工驗收早就變得不可能了。
盲點二:“它能運行併產生輸出”和“輸出正確”是兩回事。這正是ID事故的縮影:程序正常運行併產生輸出,但輸出是錯的。無聲的錯誤數據會直接通過“能用就發”的驗證——直到某一天你基於它做決策。
自動化測試直接針對這兩個盲點。通俗地説:它把你自己手動點擊的過程記錄下來,然後讓機器在每次改動後重新點擊所有舊功能,同時檢查每次輸出是否正確。這不是工程師的玄學,而是你已經在用的驗收方法的批量複製。
5. 如果你想批量讓AI工作,先給它僱個監考員
關於意大利麪條的對話後半部分,對一個氛圍編碼者提出了更實際的問題:逐個功能照看AI感覺很慢,我想進入批處理模式——一次性給出項目級規格、每個功能的計劃,然後讓AI自己批量運行。
這裏有一個我之前沒搞清楚的關係:批處理和測試不是兩件事。測試是批處理的前提條件。
當你不盯着看時,原來通過實時對話獲得的監督層消失了,必須有東西來替代。唯一能替代的就是測試:讓“所有測試通過”成為每個批次的停止條件。這樣AI完成一個批次後,如果沒通過,它會繼續自行修復;只有通過才算交卷。沒有測試的批處理,等於讓AI交一堆無人監考的考卷——而批處理正是最容易堆積混亂的模式,一個補丁在前一批次裏糊上去,就變成了下一批次的基礎。
但讓AI自己寫測試有一個著名的陷阱:它會作弊。這甚至有個正式名稱——獎勵黑客(reward hacking)。硬編碼答案(例如問1+1時,它寫“看到這個問題就回答2”而不是真的計算)、悄悄刪除無法通過的測試、寫空洞的測試看似檢查了東西實則什麼都沒驗證——它既是出題人、又是考生、還是閲卷人。
有兩種對策,都不需要你讀代碼:
對策一:考題來自規格,而不是代碼。測試應該翻譯“需求説了什麼”,而不是“代碼做了什麼”。同理,測試始終跟隨規格,而不是計劃——規格回答“怎樣才算正確”,計劃只回答“我們打算怎麼構建”。從計劃出題等於從答案出題,一旦實現變了,報警器會亂響。
對策二:讓AI用自然語言報告考題列表。你不能看測試代碼,但你能看懂“這個測試驗證:在三次錯誤密碼嘗試後,賬户鎖定”。在運行前,讓AI根據規格生成一份這樣的列表,花十分鐘掃一遍,確認沒有遺漏或被稀釋。這是你唯一需要親自做的事。
(由於原文被截斷,此處結束)