你用 AI coding 工具做出了一個功能。清單顯示正常,篩選也能用,再發一句話就能繼續修改。可有人問:為什麼這一項消失了?改掉這個條件,會不會影響其他篩選?你又得回頭問 AI。

拿一個小型 JavaScript 篩選器試試:先寫下它會留下哪些項目,再親手改庫存門檻,執行後看看結果和預測是否一致。

選一個自己能預測的行為

「教會我整個應用」太大了。先選一個能說清結果的問題:類別、搜尋文字和「只顯示有庫存」同時生效時,清單會剩下哪幾項?

修改之前,回答三個問題:

  1. 給定這組輸入,應該返回哪些項目的 ID?
  2. 每個項目被哪條條件留下或排除?
  3. 如果庫存規則變了,哪些結果仍然應該相同?

語法不熟悉時,一次讀一個表示式。這個練習中,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 快取修補程式的具體審查過程。