讓 AI 寫一個貪吃蛇遊戲,往往很快就能得到可以運行的頁面。接著加上得分、加速和暫停,改動看起來也不復雜。
但暫停按鈕出現了,遊戲就真的暫停了嗎?如果只是停止畫面渲染,後臺仍在移動和判定碰撞,玩家恢復時可能已經輸了。如果每次恢復都新建一個定時器,反覆操作後,蛇又可能越走越快。
“頁面上有這個功能”和“系統滿足這項需求”之間,隔著一整套行為約定。AI 加快了實現速度,也讓這些未經確認的細節更快進入代碼。
我更關注的,是怎樣把約定變成可恢復、可檢查的依據。下面以貪吃蛇為例,串起需求、設計與驗證。
一、讓關鍵約定脫離聊天記錄
對話適合探索。需求不明確時,可以通過追問和原型逐步收斂;局部文案調整也可以直接完成。
當功能需要持續修改,單靠聊天記錄維護項目狀態就容易出問題:早期規則埋在歷史消息裡,後續要求沒有說明哪些約定繼續有效,工具讀取的代碼又可能只覆蓋局部。上下文壓縮、檢索遺漏和相互矛盾的要求,都可能使實現偏離意圖。
關鍵約定應當能夠被重新找到:本次解決什麼問題,哪些行為必須保持,哪些不在範圍內,以及怎樣確認完成。執行任務前讀取相關內容,並與當前代碼核對,才能在換會話或繼續迭代時恢復工作。
這也是規格驅動開發(Spec-Driven Development)的起點:讓需求、設計和驗收依據隨著實現一起維護。
二、把“加個暫停”寫成行為約定
“遊戲支持暫停”至少留下了幾個問題:暫停時哪些狀態不再變化?還能否改變方向?恢復後是否補算暫停期間的時間?遊戲結束後按暫停鍵會怎樣?
EARS(Easy Approach to Requirements Syntax)通過事件、狀態和條件組織自然語言需求,幫助我們明確觸發條件與系統響應。它可以幫助檢查表達,但需求是否符合使用場景,仍需要另外判斷。
以單機版為例,可以先作出以下約定:
- P1:當運行中的遊戲收到暫停操作時,進入暫停狀態。暫停期間,蛇的位置、食物和分數保持不變。
- P2:當處於暫停狀態時,忽略方向輸入;收到恢復操作後,從當前局面繼續,暫停時長不計入後續移動。
- **P3:**當遊戲尚未開始或已經結束時,暫停操作不改變遊戲狀態。
這些約定允許我們判斷實現是否正確。例如,P1 約束了遊戲狀態本身,P2 又明確了輸入和時間的處理,避免界面停住但恢復後突然補走多步。
寫清楚後也更容易討論取捨:忽略方向輸入是否符合玩家習慣?恢復後立即移動還是等待一個完整間隔?這裡選擇後者,使恢復時刻成為新的計時起點。重要的是在實現前形成一致判斷。
同時,要區分行為要求、實現約束和範圍邊界。假設這是一個已有遊戲:暫停時保持局面是行為要求;沿用現有框架、不新增運行時依賴是本次實現的約束;賬號、排行榜和聯機功能則不在本次範圍內。寫明這些內容,可以減少 AI 為了補齊一個功能而順手重構整個項目、引入無關能力的情況。
三、從行為約定到實現約束
需求確定後,技術設計需要解釋怎樣保證它成立。
針對暫停功能,可以設計為:遊戲狀態與畫面渲染分離,由一個更新循環推進遊戲;暫停時停止推進遊戲狀態;恢復時重置計時基準,保留原有局面;循環的創建與銷燬由同一個模塊管理,避免重複啟動。
這樣,一條用戶需求就對應到了明確的實現邊界。採用哪種框架或計時 API,可以在這些約束內選擇。
如果使用文檔組織工作,可以讓每份文檔各自回答一個問題:
| 文檔 | 暫停功能中需要記錄的內容 |
|---|---|
requirements.md |
P1~P3,以及恢復後等待完整間隔的約定 |
design.md |
狀態轉換、單一更新循環、輸入處理與計時基準 |
tasks.md |
實現狀態轉換和輸入限制,處理恢復計時,補齊對應驗證 |
任務不必一次覆蓋整個遊戲。“完成暫停與恢復,並驗證不會產生重複循環”就是可以獨立審查的改動。
實現過程中如果發現設計不可行,應說明衝突和影響,再調整設計或需求。完成後,將實際差異同步到文檔。cc-sdd 等工具可以輔助組織這一流程,工具之外仍需要有人確認約定與實現保持一致。
四、讓測試有能力發現錯誤
暫停功能可以從幾個層次驗證。
先檢查狀態轉換:運行狀態可以進入暫停,暫停可以恢復,尚未開始或已經結束時不受影響。再使用可控時鐘驗證輸入與計時。
例如,將移動間隔固定為 200ms,關閉加速,並選擇不會發生碰撞的局面:暫停後推進時鐘 10 秒,蛇的位置、食物和分數都應不變;期間輸入一個正常運行時允許的轉向,恢復後的方向也應保持原樣。恢復後推進 199ms 不應移動,再推進 1ms 才移動一格。
反覆暫停與恢復後,還要檢查相同模擬時間內的移動次數,以及是否留下額外的活動計時器或更新循環。只斷言“每次回調移動一格”並不夠,因為重複循環也可能各自滿足這個條件。
最後,在瀏覽器中實際操作按鈕和鍵盤,核對顯示狀態與遊戲行為。瀏覽器調度會受到負載和後臺節流影響,規則計算適合使用可控時鐘,實際交互仍需要單獨檢查。
這裡最容易產生誤導的是:讓 AI 寫完實現,再根據實現生成測試。兩者可能共享同一個錯誤。
例如,我們另行約定加速規則:初始移動間隔為 200ms,每獲得 5 分減少 20ms,最低為 80ms。數值僅用於演示。
這裡的分數指累計得分,達到一個完整的 5 分檔位才加速。假設實現誤寫為“每 4 分加速一次”,結果會出現以下差異:
| 分數 | 按需求應得到的間隔 | 錯誤實現的結果 |
|---|---|---|
| 4 | 200ms | 180ms |
| 5 | 180ms | 180ms |
| 8 | 180ms | 160ms |
| 10 | 160ms | 160ms |
只檢查 5 分和 10 分,測試仍會通過。檢查閾值之前及兩個閾值之間,才更容易暴露錯誤。如果測試直接把錯誤實現的結果當作預期,則連這些用例也失去作用。
同樣,最低間隔也應單獨驗證:25 分時為 100ms,30 分時達到 80ms,35 分時仍保持 80ms。閾值附近與達到上限、下限後的行為,都容易被只覆蓋常規流程的測試遺漏。
因此,驗收預期應從需求獨立推導。審查測試時,我更關心它能否區分正確與錯誤的行為,而不只是覆蓋了多少行代碼。測試仍可能由 AI 輔助生成,關鍵是預期值有獨立依據,並經過審查。
五、規格清晰,也可能做錯產品
規格讓錯誤更容易被發現,但一份清晰的錯誤需求,也可以被 AI 高效地實現。
回到 P2:如果玩家希望暫停時提前調整方向,“忽略方向輸入”就可能讓操作不符合預期。實現完全符合規格,測試也全部通過,仍然需要通過原型和使用反饋決定是否修改規則。
這涉及兩種不同的判斷:實現是否符合約定,以及約定是否解決了使用者的問題。前者可以藉助測試驗證,後者需要業務確認、用戶反饋和實際使用結果。
我的取捨是,先確認會影響體驗和系統邊界的決策,再細化實現。對於仍有爭議的行為,將其標為待確認項,通過小範圍原型驗證,而不是讓 AI 把推測擴寫成一份看起來完整的規格。
六、需求變化可能觸發設計重審
如果繼續將遊戲擴展為共享場景的多人聯機版,暫停就從一個本地狀態變成了協作規則。
一名玩家暫停時,其他玩家是否繼續?暫停期間是否保留碰撞實體?斷線是否等同於暫停?這些問題會影響公平性、服務端狀態和恢復流程,原來的 P1~P3 已經不夠用了。
在這個設計推演中,需要先重新確認行為約定,再確定誰裁定關鍵狀態,以及如何將輸入和結果同步給參與者。採用權威服務器時,暫停等關鍵操作也需要經過服務端規則判斷,客戶端展示應與裁定結果保持一致。
WebSocket、網絡框架和序列化格式的選擇,隨後依據通信頻率、房間規模、允許延遲和壓測結果比較。規格變化可能使舊設計失效,架構決策也需要隨之更新。
七、控制規格的維護成本
規格與代碼共同演進,也會共同產生維護負擔。文檔描述舊行為、代碼實現新規則時,下一次 AI 修改可能繼續擴大偏差。
行為變更的審查應同時檢查需求與測試。仍待確認的假設需要標明,已經放棄的決策不應繼續作為實現依據;任務開始前,也應核對相關規格與代碼是否一致。
一次修改的完成記錄,可以對應到 P1~P3,寫明實際執行了哪些測試、結果如何、哪些瀏覽器交互已經檢查,以及仍有哪些未驗證項。任務列表上的勾選不能代替這些證據;驗收不通過時,應先判斷是實現偏離約定,還是約定本身需要修改,不能直接把預期改成當前代碼的輸出。
過程的規模可以按風險調整。局部文案修改通常只需要任務描述和頁面檢查;權限、支付、數據遷移或跨服務協作,則值得投入更明確的行為約束和失敗處理。文檔應當幫助減少誤解,而不是成為每次小改動都要跨越的門檻。
AI 降低了把想法轉成代碼的成本,也降低了把未經確認的假設轉成代碼的成本。規格、實現和驗證之間需要保留能夠相互核對的依據;熟悉業務、代碼與運行時的人,仍要判斷交付是否解決了問題,以及這些證據是否足夠可信。