用 Agent 做大型重構
Agent 寫程式很快,快到可以一口氣改壞四十個檔案。這堂課以「把記帳程式的 CSV 換成 SQLite」為例,教你用四個習慣安全地完成大型修改:先規劃、先補測試、小步前進、每步都留存檔點。
前三堂你已經有了一套不錯的工作方式:Agent 平行開發、各自有工作區、CI 自動檢查。這套方式在處理「新增一個功能」「修一個 bug」時非常順。
但有一種工作特別容易出事:大型重構。
什麼是重構?為什麼特別危險?
重構(refactoring)是改變程式的寫法,但不改變它的行為。例如把重複的程式整理成函式、把一個太大的檔案拆開、把資料的存放方式從 CSV 換成資料庫。使用者用起來應該完全一樣,只是程式變得更好維護。
重構的危險在於它碰的地方很多。你說「把 CSV 換成 SQLite」,Agent 可能會改十幾個檔案,而且改得很快。如果中間有一個地方弄錯,測試全紅,你面對一大堆修改,根本不知道從哪裡查起。
Agent 的速度是優點,但沒有煞車的速度很危險。這堂課教你裝上煞車。
接下來用一個具體的例子走一遍:前面單元做過的記帳程式 expense-app,目前用 expenses.csv 存資料,我們要把它換成 Python 內建的 SQLite 資料庫。
習慣一:先規劃,再動手
不要一開口就說「把 CSV 換成 SQLite」。先讓 Agent 只讀不改,把現況和步驟想清楚。
Claude Code 有一個計畫模式(plan mode):在這個模式下,它會讀檔案、提出計畫,但在你同意之前不會修改任何東西。在對話中按 Shift+Tab 切換,直到畫面下方顯示計畫模式;或者啟動時就指定:
claude --permission-mode plan
然後交代任務,並要求它把計畫寫下來:
我想把 expense-app 的資料儲存方式從 expenses.csv 換成 SQLite(用 Python 內建的 sqlite3),使用者操作和輸出的結果要完全不變。
請先不要修改任何檔案。請:
- 找出所有讀寫 CSV 的地方,列出檔案和函式。
- 提出一份分成 5 到 8 步的計畫,每一步都要小到可以單獨跑測試確認,並說明每一步改哪些檔案。
- 標出哪一步風險最高、為什麼。
- 寫出「這次不做的事」,避免範圍越來越大。
我確認後,請把計畫存成 REFACTOR_PLAN.md。
讀它的計畫時,問自己三個問題:每一步做完,程式還能不能跑?每一步的修改,我讀得完嗎?真實資料 expenses.csv 會不會被動到?
一份好的計畫可能長這樣:
# 重構計畫:CSV 換成 SQLite
目標:資料改存在 expenses.db,使用者操作與輸出完全不變。
這次不做:不改畫面文字、不新增功能、不刪除 expenses.csv。
1. 補測試:替 storage.py 的公開函式寫測試,鎖住目前的行為
2. 整理入口:確認 main.py 只透過 storage.py 存取資料,沒有直接讀 CSV
3. 新增 sqlite_storage.py,提供和 storage.py 一樣的函式,並通過同一組測試
4. 寫一次性的轉檔程式:把 CSV 資料複製到 SQLite(先備份)
5. 切換:main.py 改用 sqlite_storage
6. 確認一週沒問題後,再移除舊的 CSV 程式碼
注意第 6 步:舊的不急著刪。留著它,萬一新的有問題,切回去只要改一行。
習慣二:先補測試,鎖住現在的行為
重構的定義是「行為不變」,那要怎麼證明行為沒變?答案是在動手之前,先寫好測試。
這種測試的目的不是檢查程式「對不對」,而是記錄程式「現在是什麼樣子」。例如:新增三筆支出後,列出來是什麼順序?某個月的總金額是多少?金額是 0 會怎樣?
請執行 REFACTOR_PLAN.md 的第 1 步:替 storage.py 的公開函式補上測試,
記錄目前的行為。測試要使用 tests/ 裡的範例資料或暫存檔,不可以讀寫真正的 expenses.csv。
先在目前的程式上跑一次,全部通過後告訴我。這一步不要修改 storage.py。
這組測試在舊程式上要全部通過。之後每一步,它都是你的安全網:新的寫法只要讓這組測試繼續通過,就代表行為沒變。
習慣三:小步前進,一步一個循環
接下來照著計畫,一次只做一步。每一步都走同一個循環:
步驟小,每次要讀的修改就少,你才真的有辦法檢查。
交代每一步時,說清楚範圍和停下來的時機:
請執行 REFACTOR_PLAN.md 的第 3 步,只做這一步。
完成後執行 pytest,全部通過就停下來,簡短說明你改了哪些檔案,等我檢查。
你檢查時,用 git diff 看修改內容,或請 Agent 用白話說明每個檔案改了什麼。確認沒問題,就留下存檔點:
git add .
git commit -m "重構步驟 3:新增 sqlite_storage.py,通過同一組測試"
習慣四:存檔點與退路
每一步一個 commit,你就有了一連串的存檔點。萬一某一步弄得一團亂,可以直接放棄這一步還沒 commit 的修改,回到上一個存檔點重來。
Claude Code 自己也會記錄對話中的存檔點:輸入 /rewind(或連按兩次 Esc),可以把對話和檔案一起退回先前的某個時間點。這在「Agent 剛剛走錯方向」時很方便。但它是 Claude Code 的功能,Git commit 才是你真正可靠的存檔,兩個都用最安心。
另外兩個退路:
- 整個重構在一個分支或 worktree 裡做。上一堂學的工具在這裡正好派上用場:主線維持可用,重構完成、CI 通過後再合併。
- 設一條停損規則:同一步失敗兩次,就不要再叫 Agent「再試一次」。退回上一個存檔點,想想這一步是不是太大,把它拆成兩步再試。一直硬修,常常越修越亂。
什麼時候可以讓多個 Agent 一起重構?
第一堂說過,會改到同一個檔案的工作不適合平行。重構大多是這種工作,所以地基的部分要一個 Agent 依序做完。
但地基打好之後,常會出現一種適合平行的工作:大量彼此獨立、機械式的修改。例如新的函式已經寫好、測試也通過了,剩下的是把三十個不同檔案裡的舊呼叫,逐一換成新的呼叫。這時可以請 Claude Code 派出多個子代理,各自在獨立的 worktree 裡處理一批檔案,最後再一個一個合併、跑測試。
判斷方法還是第一堂那張表:彼此獨立嗎?碰的檔案重疊嗎?結果容易檢查嗎?
找一個你想整理的專案(記帳程式、課程裡做過的任何小專案都可以),選一個小型重構,例如「把重複的程式整理成一個函式」或「把一個太大的檔案拆成兩個」:
- 開一個新分支或 worktree,用計畫模式請 Agent 寫出
REFACTOR_PLAN.md,包含步驟和「這次不做的事」。 - 執行第 1 步:補上鎖住目前行為的測試,確認在舊程式上全部通過。
- 照計畫一步一步做,每一步都跑測試、讀
git diff、commit。完成後用git log --oneline看看你留下的存檔點。 - 故意在某一步請 Agent 做一個你不想要的修改,練習用
/rewind或 Git 退回上一個存檔點。 - 重構完成後推上 GitHub 開 Pull Request,讓上一堂設定的 CI 幫你確認一次。
讀完了嗎?
標記完成後,單元與專案的進度會自動更新(只存在這台裝置的瀏覽器)。
留言與討論
卡關了、有心得,或發現內容需要更新?都歡迎留言。留言使用 GitHub 帳號登入。