我曾經答應自己,從這週開始要改寫一些輕鬆點的主題。結果到了週一,我的 JSNation 之旅正式落幕,而我突然意識到,我竟然還沒寫一篇關於自己演講的文章。既然一切都還歷歷在目,那就開始吧!
至於 JSNation 本身,我的感受有點複雜。我被選進線上場次,雖然主辦方專業得令人驚嘆,但那種感覺終究還是跟親臨現場不同。你知道的,我本來可以在阿姆斯特丹跟 Wes Bos 一起喝啤酒,結果卻是在廚房裡一邊看自己的演講,一邊回 Teams 訊息。😄 不過,這仍然是很棒的體驗。誰知道呢?也許有一天,我的專業能力,或者說我的名氣等級 😅,會成長到讓我有機會親自到阿姆斯特丹演講。

話雖如此,我今年晚些時候或許還是會去那座美麗的城市,不過先別說得太早。😉
回到主題。我的演講題目是 「重寫還是重構?如何安全地將舊有應用程式遷移到現代框架」。我相信我是最適合談這個主題的人。為什麼?因為在我的職涯中,我遷移過很多前端應用程式,各種組合都有:舊版 Angular 到現代 Angular、ASP.NET MVC 5 到 Vue、使用 class component 的 React 到現代 React,諸如此類。
原因很簡單。若某家公司急需做遷移,突然收到一份履歷,而那人剛好已經做過一次這種事,通常他們都會非常樂意錄用——即使薪資比市場價高一點。😄 我大致上就是這樣成為前端遷移專家的,所以我對這件事確實有不少話想說。
在討論策略之前,我們得先回答一個更根本的問題:為什麼一開始就要遷移?
我常常從利害關係人那邊聽到這個問題,特別是非技術背景的產品負責人。既然應用程式能正常運作,為什麼要去動它?開發人員只是追逐潮流、想把新鮮技術寫進履歷,對吧?
我的回答通常很簡單,也很誠實。
你們很了解我。我不是任何特定框架的死忠粉絲。對我來說,它們只是工具,就像槌子一樣。但當我在打造某樣東西時,我寧可用一把結實可靠的槌子,也不想用一把生鏽、零件還缺東缺西的。😉🔨⛏️🪓🔧🪛
不過,遷移不只是開發者體驗的問題。說到底,它關乎你的產品能不能存活。尤其是在今天,技術演進比以往更快,而 AI 又把一切推得更快。
以資安為例。當某個函式庫已經過時,沒人再維護它時,安全性修補就不會再來了。漏洞報告開始一片紅。歷史上沒有任何一位產品負責人會說:「好啊,我們為了多幾個功能,犧牲資安!」至少我希望沒有 😅
也別忘了現代工具鏈。光是 Vite 的建置就能帶來巨大差異。現代應用程式就是比較快,而更快的應用程式就是更好的應用程式。
還有很多其他原因,但最重要的是這一點:
你越晚遷移,遷移就會變得越困難、也越昂貴。
有趣的是,LLM 的出現並沒有從根本上改變遷移策略。它們跟 AI 還沒普及之前其實差不多。工作有時會變快,有時不會,但底層做法並沒有改變。
遷移策略大概有多少,資深工程師就有多少種說法,但實務上大致可以分成兩類:
把整個應用程式全部重寫;或者,在前端領域常見的情況是,直接把一個非常古老的技術堆疊升級到框架的最新版本。
一邊持續交付新功能,一邊逐步把應用程式一塊一塊重寫。
那麼,哪一種比較好?
老實說,沒必要為這件事發動聖戰。大多數情況下,專案的現實條件會替你做決定。
如果你的應用程式相對小、團隊經驗豐富、文件也算完整,而且你能接受暫停幾週或幾個月的新功能開發,那麼重寫可能是完全合理的選擇。
另一方面,如果你面對的是一個龐大專案,團隊裡有很多初階工程師,或是對技術不熟的人,而且文件幾乎不存在——那麼漸進式重構大概就是你唯一實際可行的方案。
當然,還有很多其他因素要考慮。先快速整理如下:
| 因素 | 重寫(Big Bang) | 重構(扼殺者) |
|---|---|---|
| 應用程式規模 | 小型或中型 | 大型 |
| 功能開發 | 可暫停 | 必須持續 |
| 文件 | 良好 | 不佳 |
| 團隊經驗 | 高 | 混合 |
| 風險承受度 | 較高 | 較低 |
| 首次看到成果的時間 | 較短 | 較長 |
| 遷移期間的複雜度 | 較低 | 較高 |
| 遷移永無止境的風險 | 低 | 高 |
讓我先簡單介紹這兩種方法,先從 Big Bang 開始。
我得承認——我其實滿喜歡這種策略。沒錯,很多書籍和文章都把它描述成很高風險,有時甚至說它是一種反模式。而且如果我們談的是一個二十年歷史、沒人敢碰的 Java 單體系統,那他們說得沒錯。
但很多前端應用程式其實都還算年輕、規模也不大。在這種情況下,Big Bang 反而可能更快也更便宜。
完整重寫在現在其實已經比較少見。不過,大型升級仍然很常見,例如從 Angular 7 升級到具備 Signals 的現代 Angular,或是從舊版、使用 class component 的 React 升級到使用 hooks 的現代 React。
這些專案仍然需要大量工作,也要花很多時間翻找舊程式碼,但你通常會比扼殺者模式更快抵達終點。
我不打算在這裡細講規劃階段、遷移腳本、codemod,或我實際用過哪些工具。這些細節會因專案而異,放在一篇通用文章裡也不太有意義。
不過,有些事情幾乎在每一次遷移中都會以驚人的一致性出現。😉
這裡有個有趣的例子。有時候我們會想,某個團隊到底怎麼能讓應用程式多年不升級。其實這比想像中容易,尤其當沒有人實際在維護它,因為產品處於維運模式,而且好幾年都沒有太大變化時。
這次的情況就是如此。
後來某一天,利害關係人決定大幅擴充這個應用程式,並新增好幾個功能。我說服他們升級到最新版 Angular,主要是基於資安考量——系統裡存放的是重要資料。
簡單來說,四位開發者花了四個月把它升到最新版本。而且結果比我原先預期的還難很多。
首先,很容易低估問題的範圍。從外面看起來很簡單:「就升級 Angular 而已。」
但真正的問題通常不是框架本身,而是它周邊的生態系。
例如,我們主要的元件函式庫在 12 到 13 版之間引入了重大的語法變更。你可以想像有多少地方需要更新!當然,AI 可以幫忙,但如果某個很有企圖心的 UI 工程師決定改 CSS 類別名稱、改元件結構,AI 也救不了你——最後還是得手動修。
有些第三方函式庫早就停止維護了。這裡有件事值得記住:
如果某個函式庫多年沒更新,作者看起來也早就忘了它的存在,而且它在 Node 18 上甚至已經無法編譯,那它不是「可用的程式碼」,而是一顆定時炸彈。
沒錯,我們也有很多端對端測試。結果其中不少都因為元件函式庫的變更而炸掉了。😄 所以就算是自動化測試,也不一定能救你。
另一個重要教訓是:務必要為熱修補建立穩健的分支策略。先假設事情一定會出錯。因為最後,它大概真的會出錯。

幸好,這次遷移成功了。
建置時間大幅改善。打包體積明顯縮小。新版版面也比舊版好看得多,所以連最抗拒技術變動的利害關係人都印象深刻。
而且也許最重要的是,這個應用程式已經為未來升級做好準備。甚至連 QA 團隊——一開始很討厭我們——最後也承認,定期升級遠比每七年才升級一次要好得多 🤣
不過,有時你的應用程式實在太大,無法採用 Big Bang,或者你根本不能停止交付新功能。在這種情況下,漸進式遷移就成了唯一實際可行的選項。
這種一步一步的重構通常會透過 扼殺者模式 來實作。當然還有其他方法,但我個人很喜歡這個模式,因為它優雅、相對簡單,而且經過世界上幾家最大科技公司的實戰驗證。
那麼,扼殺者模式到底是什麼?
看看下面這張漂亮的圖吧,這是我親自在 draw.io 裡手工做出來的。😄

如你所見,你一開始先有一個舊有應用程式。然後在旁邊建立一個新應用程式。接著在前面放一個反向代理,由它決定哪些路由要導向哪一個應用程式。
從那裡開始,你就一個畫面一個畫面、一條路由一條路由地遷移。隨著時間過去,舊有應用程式會越來越小,直到最後——希望如此——只剩下新系統,然後你就可以終於把舊應用程式和反向代理都移除掉。
在這種情境下,兩個應用程式應該共用登入驗證和後端,但不要共用前端程式碼。
而且如果你非得讓兩個應用程式互相溝通,我會強烈建議你使用像自訂 DOM 事件這種簡單的方法,而不是所謂的「暫時性」轉接器。
因為我們都知道在軟體工程裡,「暫時性」是什麼意思。永遠。 😅 而且不知不覺間,那些轉接器就會變成全新的舊技術包袱。
這是一個很舊的 Backbone 應用程式——我得承認,它其實寫得非常好。不過,它還是 Backbone。😄
這個應用程式不算大,所以理論上 Big Bang 也是可行的。但當時我們是一間新創公司,有一天我們聽到那句著名的話:「各位,我們剛賣出了一個還不存在的功能。你們有三個月的時間交付。祝你們好運!」
我當下的反應是:「哈哈哈,真有趣。要我用 Backbone 做這個,絕對不可能。」 幸好,我們的利害關係人很聰明,最後同意採用扼殺者遷移。
我的團隊只有我,還有……兩位初階 Java 開發者。老實說,我發誓他們應該是人生第一次聽到「TypeScript」這個詞。
幸好他們很有企圖心,也學得很快。嗯,其實他們也沒什麼別的選擇。😄 這也是這種策略的另一個優點:初階開發者可以在過程中學習。
我另外建立了一個新的 Vue 應用程式。公司裡有很多人本來就會 Vue,所以選它很合理。
我先把登入畫面遷移過去,當作概念驗證。接著我們開始處理客戶已經付費購買的那個新功能。😉
接下來的一年,我們就一個畫面一個畫面地把整個應用程式遷移過去。

最後,Backbone 完全消失了。而且在整個過程中,我們完全沒有停機。客戶甚至沒注意到遷移正在發生。
跟前一個例子一樣,打包體積明顯變小,建置時間也大幅改善。
不過,扼殺者模式也不是只有陽光和彩虹。
首先是時間因素。遷移過程會拖很久。在我們這個相對小的應用程式裡,還是整整花了一年。
還有遷移永無止境的風險。你大概知道我在說什麼。截止日期一個接一個。新功能一直進來。過一陣子之後,你甚至會開始半懇求半哀求你的產品負責人,拜託挪幾個 story points 給遷移工作。
而且很遺憾,完全不碰到舊程式碼是不可能的。機率是零。相信我,開發者都討厭這件事。我聽過像這樣的話:「Sylwia,妳為什麼要把事情搞得這麼複雜?現在我們得同時懂兩套技術堆疊!」
所以不管你選哪一種策略,你大概都會變成團隊的替罪羊。至少在遷移進行中的時候是這樣。
但等一切結束、打包體積砍半、漏洞報告不再一片紅、客戶也不再抱怨時,你又會突然變成公司的英雄。😄
遷移舊有應用程式,很像買一棟老房子。房子快要塌掉不是你的錯,但把它修好就是你的責任。你可以花幾週時間一次全部翻新,也可以先搬進去,再一間一間慢慢整理。
唯一真正糟糕的策略,就是什麼都不做。遲早你還是得遷移——只是到時候,你是因為發生了生產環境事故才被迫遷移。😉
那麼,你會怎麼整理你的舊有應用程式?
以上描述的故事都沒有真的發生。或者有可能真的發生了?😉
身為專業人士,以及一個受 NDA 約束的人,我刻意把多個遷移故事混合在一起,確保不會辨識出任何特定公司或專案。這並不特別困難,因為過了一段時間之後,所有遷移看起來都會驚人地相似。😄
如果你喜歡我的文章,也可以在 LinkedIn 追蹤我。