Community程式設計與開發github.com

armandkaufmann/refactor-pass

找出 Git 儲存庫中需要重構及不需要重構的部分,重現開發人員手寫程式碼時的直覺。它透過量測變更的熱點(Git 歷史的熱力圖)、哪些因素讓這些檔案難以修改(程式碼壞味道,每個都有專屬的命名重構劇本),以及下一次變更的成本,然後將重構項目排序成一個可逐項處理的檢查清單。

refactor-pass 是什麼?

這個 AI 助手技能「Refactor Pass」旨在協助開發人員和 AI 代理在 Git 儲存庫中進行有重點、有紀律的重構。它透過三個可量化的維度,重現人類開發人員對何時需要重構的直覺:基於 Git 歷史的「熱力圖」找出變更頻繁的檔案,系統性地檢測「程式碼壞味道」(如過長函式、霰彈式修改),以及「逐步模擬」即將到來的變更會觸及哪些檔案。 此技能會產出一份詳細的報告,並附上一個可互動的 HTML 檢查清單,將重構項目按風險與報酬排序。它會明確標示哪些「熱門」的壞味道檔案需要重構,哪些「冷門」的壞味道檔案則應暫緩處理,以及哪些經常變更但乾淨的檔案只需確認其狀態即可。這份報告包含一個「放過清單」,明確指出不需要修改的區域,有助於避免不必要或代價高昂的修改。 支援的平台包括任何使用 Git 的開發環境,特別適用於 Claude Code、Cursor、Codex 等 AI 編碼代理。使用方式為一個結構化的 7 步驟流程:生成熱力圖、測量與審查壞味道、模擬變更、驗證發現、排序項目、建立檢查清單,最後逐步執行。此技能會將檢查清單儲存在 `.refactor-pass/` 目錄下,支援跨工作階段延續,並可依使用者要求針對整個儲存庫或特定範圍執行。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/armandkaufmann/refactor-pass/tree/HEAD

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Refactor Pass

重構曾經始於一種感覺。程式碼很難處理:一個類別你需要滾動來找到新功能該放哪,一個函式你得記在腦中才能修改某個分支,一個功能意味著要造訪八個檔案。同樣的感覺告訴你哪些可以放著不管。你從未碰過的醜陋程式碼從未傷害過你,所以你就讓它保持原樣。

寫程式的 AI 代理感受不到這些,而開發人員大多也只閱讀 AI 寫的程式碼,所以壞味道會在不知不覺中堆積。AI 代理會毫不抱怨地為一個函式加上第三十個分支。而當被要求重構時,它會清理看起來不整潔的程式碼,包括那些根本沒人碰的程式碼。這個技能重新賦予你這兩半的感覺,透過三個可量化的指標:

  1. 你會在何處感覺到:熱力圖。 摩擦只在乎變更頻繁落腳的地方。跨多項研究顯示,檔案大小和變更頻率比任何單一壞味道更能預測維護問題。
  2. 你會感覺到什麼:壞味道。 經過驗證的壞味道目錄,每個都轉換為要統計什麼、何時不是壞味道,以及數十年實務為其建立的命名重構劇本:references/smells.md。
  3. 你感覺有多強烈:逐步模擬。 拿即將到來的下一次變更(無論是計畫中或可能發生的),列出它會觸及的所有內容。

然後,最終判定結合了「何處」與「什麼」:

有壞味道無壞味道
經常變更重構確認沒問題
很少變更放著;最多加個「稍後」備註什麼都不做

「確認沒問題」這個框和「重構」這個框同等重要。這是直覺中克制的那一半,並且報告會顯示出來。

兩種問題規模。「Pass」涵蓋整個儲存庫:步驟 1 到 7。關於單一區域的問題(「我們應該重構 X 嗎?」)則僅對該區域執行相同步驟。除非有多個項目,否則它會跳過檢查清單,而其答案可以是「否」。兩種情況下,請先執行步驟 1 的腳本並加上 --checklists 參數:如果它列出了先前 Pass 的檢查清單,請先閱讀在先前的 Pass 之後一節。

1. 何處:熱力圖

python3 <此技能目錄>/scripts/heatmap.py -C <儲存庫> <範圍...>

<此技能目錄> 是存放此 SKILL.md 的資料夾。如果 python3 不是指令(例如在 Windows 上),請改用 python 或 py。

  • 範圍:原始碼資料夾和測試資料夾,例如 src tests 或 app,而不是 docs 或 assets。請閱讀專案的 README、目錄列表和任何代理指令(AGENTS.md、CLAUDE.md)來決定。測試檔案(透過名稱如 Board.spec.ts、test_x.py、XTest.php... 或資料夾識別)應排除在表格和配對之外,並且當它們與多個檔案一起變更時,會單獨列出。使用 --test GLOB 加入其他測試名稱,使用 --not-test GLOB 排除看似測試檔案的檔案(熱力圖會列出它懷疑的檔案),使用 --ignore GLOB 排除產生的檔案。
  • 視窗:預設為最近 200 個提交。當使用者指定起點時(「自 v2.0 起」、「自從我們加入計費功能後」、「最近三個月」),使用 --since 取代:指定一個標籤、分支或提交,或一個日期。機器人提交、儲存庫在 .git-blame-ignore-revs 中列出的大量格式化提交,以及先前檢查清單中記錄的提交會自動排除。使用 --exclude-commits 排除其他會讓它們觸及的所有檔案看起來很熱門的提交,例如引入的供應商程式碼。
  • 資料太少? 如果視窗內少於約 30 個提交,則計數無法區分熱門檔案和冷門檔案。請擴大視窗範圍,或者說明歷史紀錄太短而無法指出任何方向,僅根據壞味道來判斷。淺層克隆(熱力圖會這樣說)需要 git fetch --unshallow;請詢問使用者。

--help 說明每個欄位和選項。

解讀熱力圖

  • 熱點檔案有許多提交且體積也大,因此列表會優先列出這些檔案:它按提交次數乘以大小排序。小型檔案有許多提交通常沒問題,例如每個功能都會編輯的設定檔。沒人碰的大型檔案則值得放著不管。
  • 仍然很熱? recent 統計其在視窗較新的一半(一半的提交,而非一半的時間)中的提交次數,而 last 是其最新提交的日期。對照視窗的日期來解讀兩者:八個 recent 提交,全部集中在較新一半開始的那一天,代表這是一波已經冷卻的爆發。一個變小的檔案可能已經被處理過。
  • rel 是相對於檔案大小的變更行數。相對變動率預測缺陷的能力遠優於絕對變動率。遠高於 1 表示該檔案在視窗內被重寫或增長。與 growth 一起解讀:一個只增長、一次又一次提交的檔案,就是僅加法增長。在視窗內新產生的檔案,其 growth 等於其大小。
  • 配對是在相同提交中不斷一起變更的檔案。一個群組是三個或更多彼此都配對的檔案,透過變更所有這些檔案的提交來計算;重疊的群組會一起列出為一個區域。
    • 來自不同層級卻強烈配對的檔案,或跨層級的群組,通常意味著一個功能分散在它們之間(霰彈式修改)。
    • 檢查每個配對的連結方式。一個匯入、一個呼叫,或某事物命名的契約(一個路由、一個 schema、控制器與其頁面之間的型別檔案),是可見的依賴:跨越邊界的一個功能。沒有此類連結而一起變更的檔案,例如規則的兩個副本,或手動保持同步的兩個列表,則具有隱藏的依賴,這是最強的耦合類型。
    • 與許多其他檔案配對的檔案是每個變更都會通過的樞紐。
    • 一個測試檔案除了自己的受測檔案外,還與多個檔案一起變更,表示它與這些檔案的內部實作緊密耦合,會阻礙重構,除非那些檔案是一個組,而測試只是跟隨它們。
    • 如果許多提交因為檔案數量過多而無法進行配對(--max-files),例如在進行 squash-merge 的儲存庫中,請提高該限制,或將配對解讀為部分資訊。
  • 為什麼會變更。 閱讀前幾個檔案的提交訊息(--show),並按功能分組。一個檔案因多個不相關的原因而變更,就是發散式變更,而在這裡比任何計數都更直接地表現出來。

2. 什麼:壞味道

  • 針對程式碼庫本身(同一語言內)測量熱門檔案。python3 <此技能目錄>/scripts/measure.py -C <儲存庫> <範圍...> 會根據該語言的 70、80 和 90 百分位數,將每個檔案放入一個區間,而 --show <檔案> 則顯示其每個函式。「大」是 80 或 90 百分位數區間(這些區間也應用了目錄的最低門檻);70 百分位數區間則值得仔細查看。當它說百分位數傾向於某個檔案時,也要參考測量中的參考點,其中也說明了如何計算其餘項目(分支數、參數、內聚力),或如何使用專案的 linter。
  • 自行閱讀前兩個或三個核心檔案。數字指出方向;閱讀才能決定。
  • 審查其他熱點區域。如果你可以執行子代理,分配兩三個子代理各自負責一個區域(一個層級或一個目錄,這樣每個子代理會一次性讀取整個區域),並平行執行它們。根據 references/review-brief.md 為每個子代理簡報。如果沒有子代理,則自行逐一審查這些區域,並遵循相同的簡報。
  • 使用其公認的名稱來命名每個壞味道(過長函式、霰彈式修改、重複開關……),並在計入之前,根據該條目的「不是壞味道的情況」進行檢查。名稱是存取其劇本的索引。
  • 尋找缺失的概念。壞味道往往成群出現,而一個群組通常指向程式碼一直在使用但從未命名的某樣東西:一個狀態、一個類型的定義、一個設定的單一來源。請參閱壞味道一起出現。
  • 檢查哪些測試觸及了熱門程式碼:請參閱未測試程式碼。
  • 你注意到的錯誤作為 F 項目加入清單,但現在不修復。尚未重現的錯誤是一個懷疑:將其標題定為一個問題(「檢查:編輯跳過了週末規則」),並在備註中附上證據。其第一步是編寫一個會失敗的測試;如果沒有測試失敗,則跳過該項目,並附上你的發現。

3. 有多少:逐步模擬

這是最接近舊時感覺的東西。這也是讓一個項目具有說服力的關鍵。

  1. 找到接下來的變更。計畫中的變更優於推斷的變更,所以優先從使用者說即將進行的變更中獲取(「在我們開始第 7 塊之前」),然後從專案的計畫或路線圖中獲取(如果有的話),最後從提交訊息中獲取:最常出現的變更類型(「加入新的報表類型」、「加入一個設定」、「加入一個畫面」)。選取兩三種最常見的類型,而不是僅僅一種。
  2. 針對每種類型,取該類型的最新一個真實提交,並列出它觸及了哪些檔案(git show --stat)。如果它比視窗更舊,照樣使用它,並提供其日期;在當今的程式碼上進行逐步模擬,可以看出哪些東西已經移動過。如果專案的文件或代理指令有針對該變更的 recipe,請計算其步驟數。一個五步驟的 recipe 用來加入一個東西,本身就是一種承認。
  3. 逐步模擬下一個變更,但不實際撰寫程式碼。列出你必須閱讀的每個檔案、你必須編輯的每個地方,以及你必須保持同步的每個副本。
  4. 寫下當今的計數,以及在你提議的重構之後的計數:「今天加入一個新的報表類型觸及 7 個檔案和 3 個開關,重構後只會觸及 1 個檔案」。

一個冷門檔案,如果計畫中的工作會落在它上面,就不再是冷門了:將其放入表格的頂行,因為下一次變更需要支付它帶來的任何成本。如果下一次變更在今天是便宜的,那麼無論壞味道看起來如何,它的成本都很低。

4. 檢查每個發現

審查者的發現是一個線索,而非事實,你自己的初步印象也是如此。在任何項目進入清單之前,請開啟引用的行號,重新執行計數,並刪除或修正不成立的項目。在報告中說明你檢查了什麼。一個錯誤的項目可能導致使用者進行一次耗費巨大的重構,遠超過花一分鐘檢查它的成本。

然後應用克制原則。被要求找出問題的審查者總是會找到一些東西,而修復每一個發現會留下針對不可能發生的情況的額外層級和測試。排除那些冷門的項目、那些被「不是壞味道的情況」所豁免的項目,以及那些逐步模擬顯示成本低廉的項目。

5. 排序

為每個項目分配一個穩定的 ID,因為使用者會透過它來引用項目(「開始 R4」),可能在後續的工作階段中。方括號內的名稱是項目在檢查清單(步驟 6)中的欄位;F、L 和 D 項目則在 note 中說明其欄位。

  • R1, R2… 重構項目,按建議的順序排列。每個項目包含:
    • 一個以結果命名缺失概念的標題(「命名結帳流程的狀態」,而不是「重構 CheckoutController」)(title);
    • 按名稱區分的壞味道,附上數字和 file:line(smells);
    • 從步驟 3 得知,它所簡化的變更類型(eases),以及該變更的成本(cost:today 和 after);
    • 計劃,即劇本中的步驟(plan);
    • 檔案名稱、大小(S、M 或 L)和風險(低、中或高)(files、size、risk);
    • 如何檢查行為沒有改變(verify)。
  • F1… 過程中發現的小修正,每個都應該是一個獨立的提交。
  • L1… 稍後處理:有壞味道但冷門,或者值得在下次修改附近程式碼時一併處理。
  • D1… 由使用者決定:改變他們工作方式的項目,例如一個讓每個變更都變得昂貴的文件規範。列出權衡,而不是做出決定。

按報酬與風險排序。報酬是變更能減輕多少未來的工作:熱力圖和逐步模擬是證據。壞味道的優先級顯示哪些類型通常更重要。過長且複雜的程式碼,以及分散在檔案間的變更,最重要。Feature Envy 和 Middle Man 等耦合壞味道最不重要,並且通常是順便修正。

清單的順序應為:

  • 阻塞項優先:與內部實作緊密耦合的測試(會讓後續每個步驟看起來像破壞了什麼),以及沒有測試覆蓋的程式碼(其項目從固定測試開始,或明確說明無法被檢查(步驟 7));
  • 接下來是能移除複製貼上路徑的小項目;
  • 大規模改動在後面,因為它們通常需要先建立一個縫隙;
  • 任何你無法自行驗證的項目(聽起來、感覺起來或看起來如何)放在最後,因為需要使用者來檢查。

6. 撰寫檢查清單與報告

再次執行步驟 1 的指令,這次加上 --page .refactor-pass/<YYYY-MM-DD>.html,使用今天的日期。它會根據 assets/page.html 建立檢查清單,並將熱力圖寫入其 heatmap 區塊;再次執行時,它只會更新熱力圖,其他部分不變。你需要在它下方撰寫 pass 區塊,其中的 project、started、base 和 updated 已經填好:

  • summary:用兩三句話描述整體狀態;
  • items:每個項目,包含步驟 5 的欄位、status(todo、doing、waiting 等待使用者、done 或 skipped 並在 outcome 中說明原因),以及 commits 和 outcome(新項目為空);一個延續下來的項目則保留其 from。每個項目以其 ID 和狀態作為第一行開頭,例如 {"id": "R1", "status": "todo",,其後接其他欄位,如上方程式碼區塊上方的註解所示;不應使用 JSON pretty-printer 的排版方式,因為步驟 7 的狀態更新會編輯這一行;
  • left_alone:每個包含 what 和 why;
  • checks:專案的檢查方式,來自其指令,或者如果沒有任何指令,則是你將手動檢查的內容;
  • earlier:在先前的 Pass 之後,包含其 checklist 和 results,每個針對該 Pass 完成的 refactor 包含:其 id、title、eases 和 cost、接下來實際發生的 change 以及它實際 actual 觸及的內容(如果尚未發生則皆為空),以及一個 note。否則為 null;
  • log:每個包含 date 和 text,最新的在前。

每次編輯後,執行 python3 <此技能目錄>/scripts/check_page.py <頁面>,它會捕捉格式錯誤的括號、拼錯的欄位、重複的 ID 或沒有以單獨一行開頭的項目。如果你的環境可以發布或預覽 HTML 頁面,p

…

[因翻譯而截斷]

相關技能