存檔點與後悔藥 —
版本控制
這章結束你會把版本控制當成 AI 協作的安全繩:敢改、會看、能回頭。
開場 · 後悔藥不是裝飾
你請 AI 實習生幫你改一個小工具。第一版能跑,第二版加了按鈕,第三版換了顏色,第四版又請它整理版面。到了第 47 版,畫面突然空白,AI 說已經修好,但你越修越亂。最可怕的不是壞掉,而是你想不起來昨天那個「還能動」的版本長什麼樣子。版本控制就是這種時候的後悔藥。它不是工程師炫技用的黑魔法,而是幫你的專案留下存檔點:哪一版能動、改了什麼、如果走錯路要退回哪裡。對 vibe coding 來說,這不是加分題,是保命裝備。
為什麼 vibe coding 更需要存檔
寫程式最容易讓新手誤會的一件事,是以為「能跑」代表安全。其實能跑只代表此刻的畫面剛好沒有爆炸。當你請 AI 實習生連續改十幾次,專案就像一盤一直被加料的料理。加一點鹽沒問題,加一點辣也沒問題,但你沒有記錄每一步,最後味道壞掉時,就很難知道是哪一步造成的。
版本控制(version control)做的事很單純:記錄專案每個重要狀態。你可以把它想成遊戲存檔。打魔王前存一下,進新地圖前存一下,拿到重要裝備後存一下。程式也是一樣。功能剛完成、畫面剛修好、錯誤剛解掉,這些都是值得存檔的時刻。
這和普通的 cmd+Z 不一樣。cmd+Z 像是剛剛寫錯一個字時退一步,但它通常只記得很近的操作,而且關掉工具、換檔案、改了太多地方之後就不可靠。版本控制比較像把整個專案裝進時間膠囊:這一刻有哪些檔案、每個檔案內容是什麼、你為什麼留下這個版本,都一起被記住。
對純 vibe coder 來說,最常見的災難是「讓 AI 大改一次,壞掉,然後不敢再動」。你要養成的不是永遠不讓 AI 改,而是每次大改前先存檔。這會改變你的膽量。你可以放心試新的版面、新的資料結構、新的功能,因為你知道就算結果不好,也能回到上一個穩定點。膽子不是天生的,是存檔點給的。
判斷何時該存檔,可以用三個時機:第一,現在這版已經能完成一個明確任務;第二,你接下來要請 AI 做比較大的修改;第三,你剛修好一個難抓的 bug。不要等到「完美」才存。遊戲裡也不會等到全破才存檔。版本控制的價值,正是在還不完美、但已經值得保留的時候出現。
const timeline = [];
function commit(message, state) {
timeline.push({ id: timeline.length + 1, message, state });
}
commit('第一版:按鈕可以計數', 'working');
commit('第二版:加入清除按鈕', 'working');
commit('第三版:改版面,結果壞掉', 'broken');
console.log(timeline.map(save => `${save.id}. ${save.message} -> ${save.state}`).join('\n'));
commit:存檔加一句人話說明
提交(commit)就是版本控制裡的正式存檔。它不只是把檔案塞進保險箱,還會要求你寫一句訊息,說明這一版做了什麼。這句訊息看起來很小,實際上非常重要。因為兩天後你不會記得「版本 12」代表什麼,但你會看懂「加入金額四捨五入檢查」或「修正手機版按鈕太寬」。
好的 commit 訊息不用文青,不用長篇大論。它要回答一個問題:如果未來的我看到這個存檔,能不能知道它值得回來的原因?「改東西」太空。「修 bug」也太空。「修正購物車總價少算運費」就好很多。你不是在寫日記給系統看,你是在留線索給未來的自己和 AI 實習生看。
一個實用的節奏是:每完成一個小里程碑,就 commit。小里程碑不是「我今天坐在電腦前兩小時」,而是使用者看得到或測得到的變化。例如「登入表單會擋空白信箱」、「待辦事項可以新增和刪除」、「錯誤訊息改成繁體中文」。如果一次 commit 包了二十個變化,之後壞掉時就很難知道是哪個變化造成問題。
很多 AI 工具也有自己的 checkpoint 或 restore。名稱不同,本質一樣:在某個時間點留下可回復的狀態。你不一定要一開始就背 Git 指令,但你要看懂這個概念。當工具問你要不要接受某次變更、要不要建立 checkpoint,你要知道那是在問:「要不要存檔?」
commit 的習慣也會讓你的 prompt 變好。因為你要存檔,就會被迫說清楚這一版完成了什麼。這其實和 ch11 學過的規格四件套互相呼應:目標越清楚,驗收越容易,存檔訊息也越準。你不是為了 Git 而 Git,而是用 commit 把模糊的 vibe 切成一段一段可檢查的進度。
function goodCommitMessage(message) {
const vagueWords = ['改東西', '修一下', 'update', 'fix'];
const hasAction = message.length >= 8;
const tooVague = vagueWords.includes(message.trim());
return hasAction && !tooVague;
}
const messages = ['改東西', '修正手機版按鈕太寬', '加入待辦事項刪除功能'];
messages.forEach(msg => console.log(`${msg}: ${goodCommitMessage(msg) ? '可以當存檔訊息' : '太模糊'}`));
diff:看懂 AI 到底改了哪裡
差異(diff)是兩個版本的找不同。當 AI 工具準備改你的檔案時,常常會秀出一段畫面:紅色代表刪掉,綠色代表新增。你不需要一開始就看懂每個符號,但一定要養成「先掃 diff 再接受」的習慣。因為 AI 實習生有時候會順手改掉你沒請它改的地方。
讀 diff 的第一步,是先問範圍有沒有合理。你請它改按鈕文字,結果 diff 顯示它重寫整個資料計算邏輯,這就要停下來。你請它修總價錯誤,結果只改了顏色,也不合理。範圍對了,才看細節。這個動作像收外送時先看袋子是不是你的,再看裡面少不少餐點。
第二步,看刪掉的東西。新手常常只看綠色新增,覺得 AI 補了好多好像很厲害,但真正危險的常是紅色刪除。它可能刪掉驗證、刪掉錯誤提示、刪掉你前一章好不容易寫出的測試按鈕。看到刪除時,問一句:「這個刪掉後,誰會受影響?」
第三步,看變更是否符合你的目標。假設你的目標是「空白姓名不能送出」,合理 diff 應該會出現檢查姓名是否為空、顯示錯誤訊息、阻止送出。若 diff 只把按鈕改成灰色,但資料仍然可以送出,那只是看起來修好了。這和 ch14 學過的驗收很像:不要相信畫面感覺,要對照期待行為。
你不必變成職業工程師才讀 diff。你只要先抓三件事:改了哪些檔案、刪了什麼、和目標是否一致。這三件事已經能擋下大量 AI 亂改。真正的差異化就在這裡:純 vibe coder 看到綠色就按接受;你會先審稿,再讓它進專案。
const before = ['顯示商品名稱', '檢查庫存', '計算總價', '顯示結帳按鈕'];
const after = ['顯示商品名稱', '計算總價', '顯示漂亮結帳按鈕'];
const removed = before.filter(item => !after.includes(item));
const added = after.filter(item => !before.includes(item));
console.log('刪掉:' + removed.join('、'));
console.log('新增:' + added.join('、'));
console.log(removed.includes('檢查庫存') ? '警告:AI 刪掉了重要驗證' : '刪除範圍看起來安全');
回退:工業級的後悔藥
回退(restore 或 rollback)就是從某個存檔點把專案救回來。它不是承認失敗,而是讓你停止在錯路上越走越遠。很多新手卡住時會一直叫 AI「再修一次」。如果 AI 修 A 壞 B、修 B 壞 A,你其實已經不是在修 bug,而是在製造更多未知變化。這時候最成熟的動作,是回到上一個穩定 commit,重新下規格。
回退前要先決定你要救什麼。若只是剛剛改錯一句文字,用工具的復原就夠。若整個功能被 AI 改亂、檔案之間互相牽連、你已經不知道現在是哪一版,就該用版本控制回到清楚的存檔點。差別在於:小錯用橡皮擦,大亂用存檔讀取。
回退也不是把學到的東西丟掉。你可以先記下這次失敗的原因:需求不夠清楚、AI 幻覺某個不存在的資料、環境和範例不同、或改 A 壞 B。這些正是 ch16 除錯時學過的四種常見壞因。回到穩定版後,把這些邊界寫進下一次 prompt,成功率會更高。
例如你請 AI 加深色模式,結果它把原本的表單驗證刪了。你可以回退到「表單驗證穩定」那個 commit,然後重下 prompt:「只改 CSS 顏色,不要改送出流程,不要刪除任何驗證函式。完成後列出 diff 摘要。」這就是版本控制和 AI 協作真正合體的地方:先保住能動的版本,再讓 AI 在清楚邊界內動手。
實務上,回退有兩種心態。第一種是短期止血:我需要立刻回到可展示、可交付、可繼續工作的版本。第二種是長期學習:我回頭比較壞掉前後的 diff,找出哪個變更造成災難。前者讓專案活下來,後者讓你下次少踩一次。兩者都比硬凹 AI 一直修更有效。
const saves = [
{ id: 1, name: '可新增待辦', status: 'stable' },
{ id: 2, name: '加入刪除按鈕', status: 'stable' },
{ id: 3, name: '重做版面但表單壞掉', status: 'broken' }
];
function restore(saveId) {
const save = saves.find(item => item.id === saveId);
console.log(`回退到存檔 ${save.id}: ${save.name} (${save.status})`);
}
restore(2);
branch、GitHub、checkpoint:把冒險關在平行宇宙
分支(branch)是版本控制裡的平行宇宙。主線像你的正式世界,通常放穩定版本;分支像實驗世界,可以拿來試新功能。你想試深色模式、重新設計首頁、改資料格式,但又怕弄壞原本能用的工具,就開一條分支。成功了再合併(merge)回主線;失敗了就丟掉那條分支,正式世界不受影響。
這個概念對 vibe coding 特別有用,因為 AI 很適合快速試作,也很容易快速搞亂。分支讓你把「大膽」和「穩定」分開。你可以在分支裡對 AI 說:「請用完全不同的方式重排畫面。」但主線仍然保留昨天能展示的版本。這種安全感會讓你的實驗速度變快。
GitHub 可以先理解成兩件事:雲端存檔,以及全世界的 code 圖書館。雲端存檔的意思是,你的專案不只放在自己電腦,也能放到遠端。電腦壞了、換機器、要分享給別人看,都比較安全。code 圖書館的意思是,很多公開專案也放在那裡,你可以閱讀、下載、回報問題。這章不教你安裝或操作,只要先把位置感建立起來。
AI 工具裡的 checkpoint、restore、accept changes,很多都借用了同一套想法。checkpoint 是存檔,restore 是讀檔,diff 是找不同,branch 是平行宇宙。名稱可能不同,但你看懂背後動作,就不會被介面嚇到。以後工具換了,你仍然知道自己在做哪件事。
最後給你一個最小工作流:開始今天的修改前,確認目前版本能跑;請 AI 大改前,先存檔;AI 給 diff 後,先看範圍和刪除;接受後自己跑一次驗收;如果修三次還在繞圈,回退到穩定版,帶著失敗線索重寫 prompt。這五步不花俏,但足以讓你從「怕壞掉」變成「壞了也救得回來」。
const branches = {
main: ['v1 可新增待辦', 'v2 可刪除待辦'],
darkMode: ['v1 可新增待辦', 'v2 可刪除待辦', 'v3 試做深色模式']
};
const experimentWorks = true;
if (experimentWorks) {
branches.main = [...branches.darkMode];
console.log('深色模式成功,合併回 main:' + branches.main.join(' -> '));
} else {
console.log('深色模式失敗,丟棄分支,main 保持:' + branches.main.join(' -> '));
}
時光機模擬器
在視覺化時間線上改專案、建立存檔、讀 diff、回退,最後用分支試一次深色模式。 操作說明:照右側提示按鈕操作,每次都先看狀態與 diff,再決定要 commit、rollback、merge 或 discard。
Project State
Timeline
Actions / Diff
commit 是存檔
diff 是找不同
branch 是平行宇宙試作
純 vibe coder 讓 AI 大改時,常常只有「祈禱它不要壞」。你會先建立 commit,再看 diff,確認刪除範圍合理才接受。壞掉時,你不會連續叫 AI 盲修十次,而是回退到最後穩定版,帶著失敗線索重寫 prompt。要試大功能時,你會放進 branch 這個平行宇宙,成功才合併,失敗就丟棄。
檢查一下這章
下一章預告
下一章我們把前端、後端、資料庫接成一張全景圖,看懂一次請求從按鈕到資料庫的旅程。