驗證心態 —
別相信,要測試
這章結束你會把「跑起來」升級成「測過了」,用正常、邊界、惡意輸入檢查 AI 交給你的程式。
開場 · 驗證的起點
你請 AI 這位實習生寫一個折扣計算器。它很快交件,畫面也真的跑起來,甚至還會用很有自信的語氣說「功能已完成」。可是你拿 100 元打 9 折一試,結果變成 10 元。這種錯最危險,因為它不像紅色錯誤訊息那麼明顯,反而像一份看起來完整的報告。從這章開始,你不再用感覺驗收程式。你會像櫃台對帳一樣,把輸入、預期結果、實際結果一格一格核對。沒測過,就不能說完成;這就是驗證的起點。
AI 最危險的錯,是一本正經地錯
在前面幾章,你已經把 AI 當成一位很快的實習生。這個比喻在驗證時更重要:實習生可以很勤快、很會整理格式、很會模仿資深同事的語氣,但他交出來的東西仍然需要主管檢查。AI 產生的程式也是如此。它可能寫出看起來合理的函式名稱,卻呼叫了根本不存在的功能;它可能把金額公式排得很漂亮,卻把折扣方向算反;它也可能在一般數字下表現正常,一遇到 0、空白、負數就失控。
這種現象常被叫做幻覺(hallucination)。在寫程式時,幻覺不是只有「胡說八道」而已,更常見的是「九成像真的」。例如它會寫出 formatMoneyTw(),名稱聽起來像是把金額變成台幣格式,但你的專案裡沒有這個函式。瀏覽器一跑才報錯。另一種更麻煩:程式不報錯,結果卻錯。這時畫面會很安靜,使用者也可能照著錯誤結果下單、付款、寄出資料。
真實工作裡,這種安靜的錯最容易被帶到下一步。你把錯誤結果截圖給同事,同事再依照錯誤資料做表格,客服又拿那張表格回覆客人,最後大家才發現第一個計算就歪了。越晚發現,修正成本越高。驗證的價值,就是在錯誤還只是一列測試資料時攔住它,而不是等它變成一串客訴。
所以本章的第一條規則很硬:沒測過,不算完成。不是 AI 說完成,不是畫面有出現,不是按鈕按了有反應,而是你能拿出幾組輸入(input)和預期輸出(expected output),再確認實際輸出(actual output)符合。這件事聽起來像工程師儀式,其實很像生活裡的收據核對。店員說已經找零,不等於你不用看錢;系統說已經送出,不等於你不用看收件人。驗證不是不信任人,而是讓重要結果有證據。
跑起來,不等於算對
初學者最常見的誤會,是把「沒有紅字錯誤」當成「功能正確」。但程式像食譜一樣,就算每一步都能做,調味比例仍然可能錯。蛋糕烤得出來,不代表糖量正確;折扣函式會回傳數字,也不代表那個數字是你要的答案。真正的驗證要問三件事:我餵了什麼資料?我本來期待什麼結果?程式實際吐出什麼結果?
看下面這個打折函式。它沒有用任何外部套件,也不會讓瀏覽器報錯,但公式其實寫壞了。開發者想表達「打 9 折」,卻把 discountRate 當成「要扣掉的比例」。如果傳入 0.9,它會把價格乘上 1 - 0.9,也就是只剩 1 折。這正是「跑起來但不對」的經典例子。
驗證時不要只按一次按鈕。你要先用人腦算出答案,再讓程式接受考試。100 元打 9 折,預期應該是 90;200 元打 8 折,預期應該是 160。當程式印出 10 和 40,你就知道錯在公式,不是錯在畫面。請注意,這裡的重點不是你要變成數學高手,而是要養成「先有預期,再看結果」的順序。如果你沒有預期答案,只是看程式吐出一個數字,就很容易被看似合理的輸出騙過。
function discountedPrice(price, discountRate) {
return Math.round(price * (1 - discountRate));
}
console.log('100 元打 9 折,預期 90,實際:', discountedPrice(100, 0.9));
console.log('200 元打 8 折,預期 160,實際:', discountedPrice(200, 0.8));
測試三兄弟:正常、邊界、惡意輸入
測試(test)不是亂試。你可以把它分成三類,稱作測試三兄弟。第一個是正常情況(happy path):使用者照你預期的方式輸入,例如商品 500 元、重量 2 公斤、名字填「小安」。正常情況可以確認主流程通不通,但它通常抓不到最貴的 bug。因為 AI 寫程式時也最擅長滿足正常情況,它的範例常常剛好就是最漂亮的那一組。
第二個是邊界情況(edge case)。邊界就是規則剛好切換的地方,例如 0 元、滿 1000 元、剛好 5 公斤、空字串、清單沒有任何項目、數字超大。很多 bug 藏在「大於」和「大於等於」的差別裡。滿 1000 元免運,如果程式寫成 amount > 1000,那 1001 元會免運,1000 元卻不會。這不是語法錯,是規則邊界寫錯。
第三個是惡意輸入(hostile input)。這裡不是教攻擊,而是做防守常識:使用者可能輸入負數、亂碼、空白、文字代替數字,或把數量填成誇張的大值。你要確認程式不會默默接受明顯不合理的資料。就像櫃台不該接受「負三杯珍奶」的訂單,運費計算也不該接受負數重量。對完全不會寫程式的人來說,最實用的做法是把這三類寫成表格:正常測主路、邊界測切換、惡意測防呆。
function shippingFee(amount, weightKg) {
if (amount > 1000) return 0;
return 60 + weightKg * 10;
}
console.log('正常:500 元、2 公斤,預期 80,實際:', shippingFee(500, 2));
console.log('邊界:1000 元、1 公斤,預期 0,實際:', shippingFee(1000, 1));
console.log('惡意:500 元、-3 公斤,預期拒絕,實際:', shippingFee(500, -3));
手動測試 SOP:先列答案,再按執行
手動測試的 SOP(standard operating procedure,標準作業流程)很固定。第一步,寫下你要測的規則。第二步,列出幾組 input。第三步,在還沒跑程式前,先寫 expected output。第四步,執行程式,把 actual output 填上。第五步,只看「預期」和「實際」是否一致,不要被畫面漂不漂亮影響。這套流程的價值在於,它把驗收從感覺改成證據。
建議你用最笨、也最可靠的表格開始。欄位可以是:測試名稱、輸入、預期結果、實際結果、是否通過。不要一開始就追求自動化測試,也不要被術語嚇到。你現在要練的是判斷力。當你請 AI 做一個表單、計算器、清單篩選功能時,至少要列出五格:一格正常、一格空資料、一格剛好達標、一格剛好沒達標、一格亂輸入。這五格就像門口的基本安檢,擋不住全世界所有問題,但能擋掉很多低級錯。
測試表還有一個好處:它能讓溝通變得很安靜。你不用跟 AI 爭辯「好像怪怪的」,也不用跟同事說「我覺得不對」。你只要指著表格說,這列輸入是 1000 元與 1 公斤,規則寫滿 1000 元免運,所以預期是 0,但實際是 70。這種回報很短,卻很有力量。對方不需要猜你的感受,也不需要重讀整段需求,就能直接看到問題在哪裡。
下面的程式把測試表寫成陣列(array,清單),再逐一印出結果。你不需要背語法,先看它的精神:每一列都有名稱、輸入、預期。程式跑完後,不是只印答案,而是印出通過或失敗。這就是工程師常說的「可驗收」。如果一個 AI 交付結果只說「已完成」,你要追問:「你用哪些 case 測過?預期與實際各是什麼?」
function correctShippingFee(amount, weightKg) {
if (amount < 0 || weightKg < 0) return '輸入不合理';
if (amount >= 1000) return 0;
return 60 + weightKg * 10;
}
const cases = [
{ name: '正常訂單', amount: 500, weight: 2, expected: 80 },
{ name: '滿額邊界', amount: 1000, weight: 1, expected: 0 },
{ name: '負數重量', amount: 500, weight: -3, expected: '輸入不合理' }
];
cases.forEach(function(testCase) {
const actual = correctShippingFee(testCase.amount, testCase.weight);
const passed = actual === testCase.expected;
console.log(testCase.name + ':預期 ' + testCase.expected + ',實際 ' + actual + ',' + (passed ? '通過' : '失敗'));
});
把驗收條件寫進 prompt,還要抽查它
你可以讓 AI 幫你寫測試,但不能讓它自己當唯一考官。這就像請實習生寫報告,也請他自己蓋「完全正確」的章,流程看似完整,風險卻很高。比較可靠的做法,是在 prompt(提示詞)裡明確要求驗收條件:請列出正常、邊界、惡意輸入各至少兩組;請寫出每組的預期結果;請執行後貼出實際結果;如果有錯,先修正再重新跑;最後用表格回報。
這種寫法的威力在於,它把「完成」重新定義。以前你可能只寫「幫我做運費計算機」,AI 就會把畫面或函式丟給你。現在你會寫「幫我做運費計算機,規則是未滿 1000 元運費 60 元加每公斤 10 元,滿 1000 元免運,金額或重量不能是負數。完成前請用正常、邊界、惡意輸入測試,列出 input、expected、actual,全部通過才回報完成。」這段話多花十幾秒,但能省下很多事後追錯的時間。
如果這個功能牽涉金錢、名單、報名、成績或寄信,驗證更不能省。一次錯誤免運可能只是少收幾十元,但如果一天有上百筆訂單,損失會被放大;一次錯誤收件人可能只是一列資料填錯,但如果系統把通知寄給錯的人,就會變成信任問題。你不需要恐慌,也不需要一次測遍全宇宙。你只要先守住規則切換點和不合理輸入,很多昂貴錯誤就會在上線前被攔住。
不過,考官不能永遠是同一個人。AI 寫的測試也要抽查。你可以自己補一兩組它沒想到的 case,尤其是邊界值。例如它測了 999 和 1001,卻沒測 1000;它測了 2 公斤,卻沒測 0 公斤;它測了空字串,卻沒測只有空白。這些不是刁難,而是產品上線前的基本責任。職業級的差別就在這裡:不是更會喊口號,而是更早把錯誤攔下來。
const promptChecklist = [
'列出正常輸入至少 2 組',
'列出邊界輸入至少 2 組,例如剛好滿額',
'列出惡意輸入至少 2 組,例如負數或空白',
'每組都要有 input、expected、actual',
'全部通過後才說完成'
];
promptChecklist.forEach(function(item, index) {
console.log((index + 1) + '. ' + item);
});
驗證擂台:抓出運費計算機的兩個 bug
輸入測試 case,先寫你預期的結果,再按下執行;抓到兩個 bug 才算過關。
function shippingFee(amount, weightKg) {
if (amount > 1000) return 0;
return 60 + weightKg * 10;
}
- 未滿 1000 元:運費 = 60 + 重量公斤數 * 10
- 滿 1000 元含剛好 1000 元:免運
- 金額或重量小於 0:回傳「輸入不合理」
| 測試名稱 | amount | weightKg | expected | actual | 判定 | 操作 |
|---|
純 vibe coder 看到畫面能跑就交作業;你會先列 input、expected、actual。純 vibe coder 讓 AI 自己說自己完成;你會要求它附測試表,還會補測邊界值。這種行為差異會直接影響上線品質,因為你能在使用者付錢、送單、填資料之前抓到錯。工程師文化裡的 don't trust, verify,不是冷酷不信任,而是先驗證再說做完了。
檢查一下這章
下一章預告
下一章會把你剛抓到的錯,進一步拆成可修復的線索:讀懂錯誤訊息,開始 Debug AI 的 code。