寫完程式後,偵測並清理三個層級的程式碼重複——複製貼上區塊、跨套件共享程式碼、不必要的包裝函式,以及概念層級的單一事實來源(SoT)違規。使用 indexion 偵測、修復並驗證。
indexion refactor — 程式碼重構
使用 indexion 的分析指令,偵測並消除三個層級的重複——文字層級、結構層級與概念層級——然後驗證 SoT 是否被強制執行。
使用時機
- 加入新的抽象(型別、模組、API 層)之後
- 引入新的檔案格式或 I/O 邊界之後
- 修復一個問題需要觸及 3 個以上檔案時
- 為了繞過結構性問題而加入「防護」或「跳過」邏輯時
- 從非預期路徑出現
opendir、ENOENT或類似檔案系統錯誤時 - 跨套件提取共享程式碼時
- 重構後清理(移除瑣碎的包裝函式)時
- 定期對程式碼庫進行 SoT 健康檢查
三個層級的重複
| 層級 | 說明 | 工具 | 範例 |
|---|---|---|---|
| 文字層級 | 複製貼上的程式碼區塊、完全相同的函式 | plan refactor |
is_whitespace 在 5 個模組中被複製 |
| 結構層級 | 相同邏輯結構但不同名稱 | plan solid、plan unwrap |
跨套件提取候選、瑣碎的包裝函式 |
| 概念層級 | 相同領域概念被獨立實作 | explore + 人工分析 |
三個模組各自判斷「這個檔案是不是壓縮檔?」 |
文字層級重複容易發現與修復。概念層級重複最困難也最危險——它不會產生複製貼上的匹配,但修改一個概念需要更新所有分散的實作。
工作流程
階段 1:清除文字層級重複(plan refactor)
從高信賴度的匹配開始,逐步降低門檻。
# 步驟 1:找出 90% 以上的重複(高信賴度)
indexion plan refactor --threshold=0.9 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
cmd/indexion/
indexion plan refactor --threshold=0.9 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
src/
將輸出分為三個部分閱讀:
| 部分 | 發現內容 | 處理方式 |
|---|---|---|
| 相似檔案 | 整體相似度高的檔案 | 調查是否需結構合併 |
| 重複程式碼區塊 | 檔案間行層級完全相同的程式碼 | 提取至 @common 或共享模組 |
| 函式層級重複 | 結構相似的函式(對函式主體做 TF-IDF) | 統一為單一 SoT 函式 |
同檔案內的重複(同一檔案內相似度 90% 以上的函式)是最高價值的目標——最容易修復,效益最明確。例如:get_global_data_dir 和 get_global_cache_dir 共享 95% 的結構,可提取為 resolve_os_dir。
# 步驟 2:在合併前使用 grep 追蹤參考
indexion grep "TypeIdent:TfidfEmbeddingProvider" src/
indexion grep --semantic=name:is_whitespace src/
# 步驟 3:修復,然後重新執行以確認重複已消失
indexion plan refactor --threshold=0.9 --include='*.mbt' ...
# 步驟 4:降低門檻並迭代
indexion plan refactor --threshold=0.85 --include='*.mbt' ...
plan refactor 選項:
| 選項 | 預設值 | 說明 |
|---|---|---|
--threshold=FLOAT |
0.7 | 最小相似度門檻 |
--strategy=NAME |
hybrid | 相似度演算法:hybrid、tfidf、bm25、jsd、ncd |
--fdr=FLOAT |
0 | FDR 校正(0=停用) |
--style=STYLE |
raw | 輸出格式:raw、structured |
--format=FORMAT |
md | 輸出格式:md、json、text、github-issue |
--name=NAME |
-- | 專案名稱(用於 structured 風格) |
--include=PATTERN |
-- | 包含模式(可重複) |
--exclude=PATTERN |
-- | 排除模式(可重複) |
-o, --output=FILE |
stdout | 輸出檔案路徑 |
--specs-dir=DIR |
kgfs | KGF 規格目錄 |
清理後仍保留的內容(停止訊號):
- 平台樁程式(
native.mbt/stub.mbt)——有意的平台分支 - 型別方法相似性(不同型別的
to_string)——不同型別,相同模式 - CLI 指令樣板(
command()函式)——@argparse API 模式,非重複 - 語義不同但相似的函式(
is_disqualifying_keywordvsis_skip_token)——目的不同
階段 2:提取跨套件共享程式碼(plan solid)
在清理每個目錄內部的重複後,找出應跨套件共享的程式碼。
# 找出兩個套件之間的重疊
indexion plan solid --from=src/a,src/b
# 指定提取目標
indexion plan solid --from=src/a,src/b --to=src/common
# 使用樹編輯距離進行精確的函式層級匹配
indexion plan solid --from=src/a,src/b --strategy=apted
# 使用較高門檻進行更嚴格的匹配
indexion plan solid --from=src/a,src/b --threshold=0.95
# 過濾檔案
indexion plan solid --from=src/a,src/b --include='*.mbt' --exclude='*_test.mbt'
plan solid 與 plan refactor 的差異:
plan refactor |
plan solid |
|
|---|---|---|
| 範圍 | 目錄內部的重複 | 跨目錄的重疊 |
| 目標 | 合併程式碼庫內部的重複 | 將共享程式碼提取到新套件 |
| 輸入 | <path> |
--from=dirA,dirB |
plan solid 選項:
| 選項 | 預設值 | 說明 |
|---|---|---|
--from=DIRS |
(必填) | 來源目錄(逗號分隔或可重複) |
--to=DIR |
-- | 提取目標目錄 |
--rules=FILE |
-- | 規則檔案(.solidrc) |
--rule=RULE |
-- | 內聯規則(可重複) |
--threshold=FLOAT |
0.9 | 最小相似度門檻 |
--strategy=NAME |
tfidf | 相似度演算法:tfidf、apted、tsed |
--include=PATTERN |
-- | 包含模式(可重複) |
--exclude=PATTERN |
-- | 排除模式(可重複) |
--format=FORMAT |
md | 輸出格式:md、json、github-issue |
-o, --output=FILE |
stdout | 輸出檔案路徑 |
--specs-dir=DIR |
kgfs | KGF 規格目錄 |
工作流程:
- 先對每個目錄個別執行
plan refactor清理內部重複 - 執行
plan solid --from=dirA,dirB找出跨目錄的提取候選 - 依照計畫的建議提取共享程式碼
- 使用
indexion grep "TypeIdent:SharedType"驗證所有參考都已更新
階段 3:移除不必要的包裝函式(plan unwrap)
在合併完成後,清理那些只增加間接層而無價值的瑣碎委派函式。
# 步驟 1:快速檢查
indexion grep --semantic=proxy src/
# 步驟 2:詳細報告
indexion plan unwrap --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# 步驟 3:預覽變更(安全——不修改檔案)
indexion plan unwrap --dry-run --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# 步驟 4:套用修復
indexion plan unwrap --fix --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# 步驟 5:執行測試
moon test --target native
會被偵測的內容: 函式主體只有單一函式呼叫,且所有參數都以簡單識別碼轉發——沒有控制流程,沒有轉換。
// 被偵測(預設)——瑣碎委派
fn matches_pattern(text : String, pat : String) -> Bool {
@glob.glob_match(text, pat)
}
// 預設排除(使用 --all 包含)
fn length(self : MyList) -> Int {
self.items.length() // 自我委派(封裝)
}
fn emit(value : String) -> Action {
Emit(value) // 裸建構子
}
plan unwrap 模式:
| 模式 | 旗標 | 說明 |
|---|---|---|
| 報告 | (預設) | 列出找到的包裝函式 |
| 預覽 | --dry-run |
顯示所有編輯而不修改檔案 |
| 修復 | --fix |
將編輯套用到檔案 |
plan unwrap 選項:
| 選項 | 預設值 | 說明 |
|---|---|---|
--dry-run |
-- | 預覽編輯 |
--fix |
-- | 套用編輯 |
--all |
-- | 包含自我委派與裸建構子包裝函式 |
--include-self |
-- | 包含 self.field.method 模式 |
--include-bare |
-- | 包含裸建構子包裝函式 |
--include=PATTERN |
-- | 包含模式(可重複) |
--exclude=PATTERN |
-- | 排除模式(可重複) |
--format=FORMAT |
md | 輸出格式:md、json、text |
-o, --output=FILE |
stdout | 輸出檔案路徑 |
--specs-dir=DIR |
kgfs | KGF 規格目錄 |
移除前請審查:
- 平台包裝函式(FFI、
@osenv_path)是抽象層,非意外間接層 - 被外部套件使用的公開 API 包裝函式——移除它們會造成破壞性變更
- 務必先執行
--dry-run
階段 4:偵測概念層級重複(explore + 分析)
這是最困難的層級。文字與結構工具無法找到它,因為程式碼不同——但概念相同。
# 找出哪些檔案共享詞彙(= 在同一個概念領域中運作)
indexion explore --threshold=0.4 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
src/ cmd/
相似度在 40-60% 且無結構重複的檔案是概念鄰居——它們使用相同詞彙是因為處理相同領域。
對於每個高相似度配對,問:「它們共享什麼概念,誰擁有它?」
# 使用樹結構比較檢查共享詞彙
indexion explore file_a.mbt file_b.mbt --threshold=0 --strategy=apted
常見的概念洩漏模式:
| 症狀 | 洩漏的概念 | 修復方式 |
|---|---|---|
兩個檔案都呼叫 is_X(spec) 然後 Y::from_spec(spec) |
「判斷是否為 X 並設定 Y」 | 將 try_do_X(path, spec) 提取到擁有 X 的模組 |
兩個檔案都在內容已載入時呼叫 @fs.read_file_to_string(path) |
「讀取檔案內容」 | 將內容作為參數傳遞,不要重新讀取 |
兩個檔案都執行 parent_dir(path) 然後 @fs.read_dir(dir) |
「列出同層檔案」 | 將目錄走訪集中到管線中 |
多個 if is_virtual_path(x) { skip } 防護 |
「真實 vs 虛擬路徑」 | 讓型別系統防止虛擬路徑到達此處 |
兩個檔案都執行 buf.write_string("\n"); buf.write_string(x) |
「連接文字條目」 | 將 join_text_entries() 提取到擁有模組 |
階段 5:合併到 SoT
定義概念的模組應該是唯一實作邏輯的模組。
規則:
- 一個概念,一個模組,一個函式。 如果「從壓縮檔提取文字」出現在
vfs.mbt、discover.mbt和args.mbt,它只應屬於vfs.mbt。 - 呼叫者接收結果,而非原料。 不要分別匯出
is_archive_spec+ArchiveSpec::from_spec+expand_archive。匯出try_extract_archive_text(path, spec) -> String?。 - 防護是症狀,不是修復。
if is_virtual_path(x) { skip }表示虛擬路徑根本不該到達此處。修復來源,而非接收端。 - 從磁碟重新讀取已在記憶體中的內容是概念洩漏。 如果
SupportedFile.content已持有文字,下游程式碼不應呼叫@fs.read_file_to_string(file.path)。
階段 6:驗證
# 確認文字層級重複已消失
indexion plan refactor --threshold=0.9 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
src/ cmd/indexion/
# 確認概念相似度已降低
indexion explore file_a.mbt file_b.mbt --threshold=0
# 確認包裝函式已清理
indexion plan unwrap --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# 執行測試
moon test --target native
SoT 合併後:
- 概念擁有者與其呼叫者之間的文字相似度下降
- 呼叫者變得更短(單一 API 呼叫取代多步驟邏輯)
- 概念擁有者可能變大,但它是唯一需要修改的地方
階段 7:用測試證明不再復發
撰寫一個從結構上防止舊模式復發的測試:
test "SoT: SupportedFile.path 永遠是真實的檔案系統路徑" {
// 建立壓縮檔,執行 load_supported_file_info
// 斷言:沒有路徑包含 "!/"
// 斷言:每個路徑都通過 @fs.path_exists
}
測試不檢查行為——它檢查的是 SoT 不變量。
紅旗
「我需要在這裡加入一個防護」
如果你正在對一個不該接收特殊情況的函式加入 if is_special_case(x) { skip },問題在上游。該函式的呼叫者永遠不該傳入那個值。
「它運作正常,但會將錯誤輸出到 stderr」
來自 C 執行時的 stderr 訊息(opendir: No such file or directory)表示無效資料到達了系統呼叫。catch 吸收了錯誤,但 perror() 已經輸出了。唯一的修復方式是防止無效資料到達該呼叫。
「我會在每個指令中分別修復」
如果相同的修復需要在 explore、search、grep、reconcile、plan documentation 中進行……修復應屬於共享管線,而非每個指令。
「相似度只是共享詞彙,不是真正的重複」
在不該共享概念的模組之間出現 40-60% 的 TF-IDF 相似度是一個警告。詞彙匹配本身就是訊號。
快速參考:何時使用哪個指令
| 問題 | 指令 |
|---|---|
| 「哪些檔案相似?」 | explore --format=list |
| 「到底什麼被重複了?」 | plan refactor --threshold=0.9 |
| 「套件 A 和 B 之間有哪些程式碼重疊?」 | plan solid --from=A,B |
| 「哪些函式是瑣碎的包裝函式?」 | plan unwrap 或 grep --semantic=proxy |
| 「這些檔案共享什麼概念?」 | explore file_a file_b --threshold=0 --strategy=apted |
| 「重複被修復了嗎?」 | 以相同門檻重新執行 plan refactor |






