你用 AI coding 工具做出了一個功能。清單顯示正常,篩選也能用,再發一句話就能繼續修改。可有人問:為什麼這一項消失了?改掉這個條件,會不會影響其他篩選?你又得回頭問 AI。
拿一個小型 JavaScript 篩選器試試:先寫下它會留下哪些項目,再親手改庫存門檻,執行後看看結果和預測是否一致。
選一個自己能預測的行為
「教會我整個應用」太大了。先選一個能說清結果的問題:類別、搜尋文字和「只顯示有庫存」同時生效時,清單會剩下哪幾項?
修改之前,回答三個問題:
- 給定這組輸入,應該返回哪些項目的 ID?
- 每個項目被哪條條件留下或排除?
- 如果庫存規則變了,哪些結果仍然應該相同?
語法不熟悉時,一次讀一個表示式。這個練習中,filter 留下回撥返回 true 的項目;&& 要求條件同時滿足;|| 兩邊滿足任意一邊即可。資料和結果都很小,可以先在紙上算一遍。
下載篩選學習練習。程式碼和四條資料為本文獨立構造,不是抓取的 AI 回答,也不來自真實應用。解壓後開啟 filter-learning-lab。需要 Node.js 22 或更新版本、文字編輯器和終端;無需安裝依賴、呼叫模型或存取網路。
先寫下三個預測,再執行
開啟 catalog.mjs 和 filter.mjs。資料只有四條:
| ID | 名稱 | 類別 | 庫存 |
|---|---|---|---|
folded-stars |
Folded stars | paper |
1 |
paper-kites |
Paper kites | paper |
3 |
clay-moons |
Clay moons | clay |
0 |
clay-bells |
Clay bells | clay |
2 |
原來的庫存規則是至少有一件。搜尋匹配名稱,忽略字母大小寫,也去掉查詢兩端的空白。類別、搜尋和庫存條件必須同時滿足;類別為 all 時接受所有類別,搜尋為空時匹配所有名稱。
開啟 blank-note.md,先寫出以下三組輸入會留下哪些 ID。可以反覆讀函式,暫時不要執行觀察命令,也別先讓 AI 給答案。
| 情況 | 類別 | 搜尋文字 | availableOnly |
要說明什麼 |
|---|---|---|---|---|
| A | paper |
空 | true |
為什麼庫存為 1 也符合條件 |
| B | all |
CLAY |
false |
為什麼零庫存項目仍能出現 |
| C | clay |
bells |
true |
三個條件怎樣一起篩選 |
寫完後,在解壓目錄執行:
node observe.mjs
node --test baseline.test.mjs
展開核對原始結果
| 情況 | 原始 ID | 原因 |
|---|---|---|
| A | folded-stars、paper-kites |
都是紙類,庫存也都至少為 1。 |
| B | clay-moons、clay-bells |
搜尋去掉首尾空白、忽略大小寫後匹配兩項;庫存篩選開關關閉。 |
| C | clay-bells |
類別、名稱都匹配,庫存為 2。 |
預測不同時,找出自己最先讀錯的條件。比如 B 不能解釋成「這兩項都有庫存」:clay-moons 的庫存是 0。它能留下,是因為 availableOnly 為 false,這個條件沒有按庫存排除項目。
不借用答案,自己解釋一次
如果看過已填寫的筆記,先把它關掉。用兩三句話向一個正在看資料表的人解釋 B,寫清實際輸入,以及哪條條件允許零庫存項目留下。
把幾個檔案各自的工作分開記:
| 檔案 | 在練習裡負責什麼 |
|---|---|
catalog.mjs |
提供四條資料。 |
filter.mjs |
根據傳入條件決定留下哪些項目。 |
observe.mjs |
傳入三組條件,列印篩選後的 ID 和庫存。 |
baseline.test.mjs |
檢查原來的篩選行為。 |
這裡的觀察器從終端呼叫函式,沒有瀏覽器介面。換成自己的應用時,輸入可能來自按鈕或表單;要讀呼叫方,才能知道介面實際傳了什麼。
可以用 Yenpo 的本機程式碼閱讀工具把資料、篩選函式和呼叫方一起開啟。檔案搜尋、全文搜尋不需要連線模型。設定自己的模型服務商後,也可以向 AI Reader 問一個卡住的條件,再回到附近的原始碼,自己寫出解釋。命令由你在 Terminal 中執行,這個練習不需要模型回答。
親手改一條規則
現在需求變了:開啟庫存篩選時,只顯示足夠兩個人使用的材料,每人需要一件。類別篩選、搜尋文字的處理方式,以及關閉庫存篩選時的行為都保持不變。
原來的「至少一件」符合原需求。這次是在改功能,不是在修一個故意藏起來的錯誤。
動手前再寫一次預測。A 應該少掉 folded-stars;B 的開關關閉,仍應包含零庫存項目;C 中的 clay-bells 恰好有兩件,應該繼續留下。
先讓新需求的檢查跑一次原始碼:
node --test exercise/pair-policy.check.mjs
修改之前,這條命令預期會失敗,因為下載的函式仍接受庫存為 1 的項目。讀失敗的斷言,指出哪項新需求尚未實作。語法錯誤、找不到檔案是另一類失敗,不能混在一起。
在 filter.mjs 中,只把庫存門檻從 >= 1 改成 >= 2,保留周圍的 availableOnly 判斷。然後執行:
node observe.mjs
node --test exercise/pair-policy.check.mjs
| 情況 | 修改後的 ID | 檢查了什麼 |
|---|---|---|
| A | paper-kites |
一件材料不再滿足選中的雙人規則。 |
| B | clay-moons、clay-bells |
關閉庫存篩選時仍不限制庫存;搜尋仍去除首尾空白、忽略大小寫。 |
| C | clay-bells |
恰好兩件也符合條件;誤改成 > 2 會把它排除。 |
原來的 baseline 測試描述的是一件即可的規則,改變需求後,受影響的斷言會失敗。新行為用單獨命名的雙人規則檢查來驗證,不要為了掩蓋失敗直接刪斷言。壓縮包保留原始原始碼,想重做時可以重新解壓。
再解釋一次,包括沒變的部分
暫時合上完成版示例,用自己的話寫完筆記。至少說清:
- 原門檻、新門檻分別是什麼,改了哪一處。
- 為什麼 A 少了一項,B 卻沒變。
- 為什麼 C 中庫存恰好為 2 的項目仍應符合條件。
- 跑了哪些命令,各自看到了什麼。
- 還沒檢查什麼,例如真實應用的表單輸入、服務端行為和資料儲存。
具體的解釋可以是:「開著庫存篩選時,所有條件都要滿足,現在庫存還必須至少為二。關掉後,庫存不影響篩選。因此 A 去掉了 folded-stars,B 和 C 的 ID 不變。」關掉這篇文章,再看著程式碼講一次,不用背這段話。
接著換一組輸入。先預測結果,再執行,看看自己能否說清每個項目為什麼留下或被排除。
回到自己的專案,一次只學一個功能
選一個輸入清楚、輸出能重現的小行為。記下原始碼版本,保留副本或使用平時的版本管理方式,第一次只做很小的修改。執行前先寫預測;結果不同時,先讀相關條件和呼叫方,再決定是否讓 AI 繼續產生修補程式。
還找不到相關程式碼時,先看怎樣找到陌生程式碼儲存庫的入口。需要判斷 AI 的某段解釋是否正確,做解釋核驗練習。正在審查一份改動,則可以看 Promise 快取修補程式的具體審查過程。