【Android 架構系列 09】遺產繼承 (Legacy Code):絞殺榕模式 (Strangler Fig)——如何利用 AI 安全地重構一座屎山
01. 前言:為什麼 AI 重寫 (Rewrite) 總是失敗?
這是系列文的第九篇。
在前八篇,我們都在討論如何「從零開始」打造完美的架構。但現實世界往往沒那麼美好。90% 的開發者加入團隊時,面對的是一座已經運作多年、充滿歷史包袱的 Legacy Code (遺產代碼)。
面對這座「屎山」,我們最想做的事通常是叫 AI:「把這整個 Activity 重寫成 MVVM + Compose。」
千萬別這麼做。 這就像是試圖在飛行中的飛機上更換引擎,結果通常是墜機(引入了更多 Bug,遺漏了隱藏的業務邏輯)。
這篇將介紹一種源自系統架構的經典模式——絞殺榕模式 (Strangler Fig Pattern),並教你如何利用 AI,像植物的氣生根一樣,一點一點地把舊代碼「吸乾」,最終安全地完成重構。
當你把一個 2000 行的 LegacyActivity.java 丟給 AI,說:「請用 Kotlin + MVVM 重寫它。」
AI 會很興奮地幫你生成一個漂亮的 ViewModel 和 Repository。但當你跑起來時,會發現:
- 某個特定的 Error Code 處理邏輯不見了。
- Analytics 的埋點事件漏掉了。
- 原本「先讀 DB 再打 API」的複雜順序被簡化成了「直接打 API」。
失敗原因: Legacy Code 裡藏著無數「隱性知識 (Tacit Knowledge)」。這些邏輯往往是為了修復某個 3 年前的詭異 Bug 而加上去的,沒有文件,只有代碼知道。AI 在一次性重寫時,很容易把這些「雜訊」當作無用代碼過濾掉。
所以,我們不能重寫,只能 「絞殺」。
02. 核心戰略:絞殺榕模式 (Strangler Fig Pattern)
絞殺榕是一種植物,它不會直接推倒大樹,而是從樹冠開始長出氣生根,包圍樹幹,與大樹共存。隨著時間推移,氣生根越來越粗壯,最終取代了大樹的支撐功能,原有的樹幹則枯死消失。
在 Android 重構中,這意味著:
不要刪除舊的 Activity,而是在它旁邊建立新的架構 (Domain/Data Layer),然後讓舊 Activity 去呼叫新架構,直到舊邏輯完全被掏空。
03. 實戰演練:四步絞殺法
假設我們有一個 OldNewsActivity,裡面用 AsyncTask 和 HttpURLConnection 寫滿了邏輯。
第一步:種下種子 (建立並行架構)
不要動 OldNewsActivity 的任何一行代碼。
請 AI 在旁邊建立標準的 Clean Architecture 目錄結構。
Prompt:
"請建立 domain/model/News.kt 和 data/repository/NewsRepository.kt。 參照 OldNewsActivity 的 JSON 解析邏輯,定義乾淨的資料結構與介面。"
這時候,你的專案裡同時存在「舊邏輯」與「新架構」,它們互不干擾。
第二步:生長氣生根 (抽取 UseCase)
這是 AI 最擅長的工作。我們要請 AI 從舊代碼中「萃取」業務邏輯,變成 UseCase。
Prompt:
"閱讀 OldNewsActivity 中的 parseJsonAndFilter 方法。 請將其中的過濾邏輯(例如過濾掉沒有圖片的新聞)提取出來,實作為 FilterNewsUseCase。 請為這個 UseCase 撰寫單元測試,確保邏輯與舊代碼一致。"
關鍵點: 我們利用單元測試來鎖定行為。如果 AI 提取的邏輯能通過測試,我們就有了信心。
第三步:纏繞樹幹 (在舊 UI 中呼叫新邏輯)
現在,我們要讓舊的 Activity 開始依賴新的架構。我們不改 UI,只改邏輯來源。
Prompt:
"修改 OldNewsActivity。
- 加入 NewsRepository 的依賴(如果是 Java 專案,可以用手動注入或 Hilt 的 EntryPoint)。
- 在 loadData() 方法中,停止使用 AsyncTask。
- 改為呼叫 newsRepository.getNews()。
- 將回傳的 Domain 物件轉換回舊的 Adapter 需要的格式,並顯示。"
// OldNewsActivity.kt (被絞殺中)
class OldNewsActivity : AppCompatActivity() {
// 注入新的 Repository (氣生根插進來了)
@Inject lateinit var newRepository: NewsRepository
fun loadData() {
// ❌ 刪除舊的 AsyncTask
// new DownloadTask().execute();
// ✅ 使用新的架構取得資料
lifecycleScope.launch {
val result = newRepository.getNews()
// 適配層:把新資料轉給舊 UI 用
val oldDataFormat = result.map { it.toLegacyFormat() }
oldAdapter.setData(oldDataFormat)
}
}
}
筆者點評:
這一步完成後,App 的外觀完全沒變,但內部的血管已經換成了新的 Retrofit + Repository。我們成功地把「資料層」絞殺了。
第四步:宿主死亡 (替換 UI)
當 OldNewsActivity 裡面的邏輯只剩下「呼叫 Repository」和「更新 View」時,它就已經被掏空了。
這時候,你就可以放心地讓 AI 寫一個全新的 NewsScreen (Compose),直接對接 NewsViewModel,然後把 OldNewsActivity 刪除。
04. 絞殺過程中的 AI 提示技巧
在重構過程中,AI 很容易「自作聰明」去優化舊代碼。我們要嚴格禁止。
- Bad Prompt: "幫我優化這段 Legacy Code。" (AI 會改寫邏輯,導致風險)
- Good Prompt: "請閱讀這段代碼,不要改變行為,將其邏輯提取到新的 UseCase 中。請保持原本的變數命名以便對照。"
05. 結論:溫水煮青蛙的藝術
重構 Legacy Code 不是一場革命,而是一場 「和平演變」。
透過絞殺榕模式,我們利用 AI 強大的「理解」與「生成」能力,但限制了它的破壞力。
- 新建 乾淨的架構 (AI 擅長)。
- 提取 邏輯並測試 (AI 擅長)。
- 橋接 新舊世界 (由人類控制風險)。
- 替換 舊 UI (最後一步)。
這樣,即使面對十年的屎山,你也能優雅地、安全地,把它變成一座現代化的花園。
重構完成了,架構也現代化了。但我們怎麼確保在未來的迭代中,這座花園不會再次荒廢?
這就需要最後一步:自動化驗收。
下一篇最終章,我們將總結全系列,並探討 TDAD (測試驅動 AI 開發),讓單元測試成為你永遠的守門員。
下集預告:【Part 10】最終驗收:測試驅動 AI (TDAD)——讓單元測試成為你的自動化質檢員

關於作者
Ken Huang
熱衷於嘗試各類新技術的軟體開發者,現階段主力為 Android / iOS 雙平台開發,同時持續深耕前端與後端技術,以成為全端工程師與軟體架構師為目標。
最廣為人知的代表作為 BePTT。開發哲學是「以做身體健康為前提」,致力於在工作與生活平衡的基礎上,打造出擁有絕佳使用體驗的軟體服務。
這裡是用於紀錄與分享開發經驗的空間,希望能透過我的實戰筆記,幫助開發者解決疑難雜症並提升效率。
系列文章目錄
- 【Android 架構系列 06】錯誤處理 (Error Handling):別讓 AI 吞掉你的 Exception
- 【Android 架構系列 07】狀態的藝術:Presentation Layer 設計——用 Sealed Interface 終結變數大爆炸
- 【Android 架構系列 08】現代化 UI (Compose):打造「笨」元件——利用 Preview 與 Slot API 讓 AI 生成完美的無狀態 UI
- 【Android 架構系列 09】遺產繼承 (Legacy Code):絞殺榕模式 (Strangler Fig)——如何利用 AI 安全地重構一座屎山 (本文)
- 【Android 架構系列 10】最終驗收:測試驅動 AI (TDAD)——讓單元測試成為你的自動化質檢員