模組四|核心系統改寫(Day 17–21)
先給一個看起來很糟的數字。
舊系統負責「路由與選單」的程式碼,散在四個檔案裡,合計 328 行。
新系統重寫之後,同樣的職責變成 704 行。
兩倍以上。
如果你的重構 KPI 是「減少程式碼」,這一篇會很難交代。但我想論證的是:這個變化是對的,而且我們刻意讓它發生。
Vue Router 有個機制叫「導航守衛」:每次使用者切換頁面之前,先跑一段程式碼。你可以在這裡檢查登入狀態、擋掉沒權限的頁面。
舊系統只用了一個守衛。而那個守衛裡面,做了這些事:
| 它在做什麼 | 屬於什麼職責 |
|---|---|
| 啟動頁面頂端的進度條 | 視覺回饋 |
| 檢查有沒有 token | 認證 |
| 判斷是不是鎖屏狀態,是的話導去鎖屏頁 | 認證 |
| 把這一頁加進上方的多頁籤 | 版面狀態 |
| 沒有 token 就導去登入頁 | 認證 |
| 設定瀏覽器分頁標題 | 視覺回饋 |
六件事,三種完全不同的職責,擠在同一個函式裡。
這正是 Day 4 講過的那把七合一工具,只是換了個位置:六種職責長在同一個握柄上,你想修其中一個,其他五個都要一起停用。
而路由守衛比那把工具更麻煩的地方在於——它每一次頁面切換都會跑。所以任何一段出錯,影響的不是某個功能,是全站。
實務上這造成什麼?Day 3 那個真實案例就是:有人為了修「切回舊分頁時搜尋條件消失」,改動了多頁籤那一段。而那段程式碼跟認證邏輯寫在同一個函式裡,所以改它的人必須先讀懂認證,才敢動手。

新系統把這件事拆成九個獨立的守衛,每個只做一件事:
頁面狀態 → 載入狀態 → 取消未完成的請求 → 捲動位置
→ 訊息提示 → 進度條 → 權限 → 頁面狀態收尾 → 參數選單
它們被統一註冊在一個地方,順序明確。
行數變多,就是變在這裡。 九個檔案各自有 import、有型別、有函式簽名,這些是「拆開」的固定成本。
那為什麼值得?
因為現在「改進度條」和「改權限」是兩個檔案的事。你不需要為了調整一個載入動畫,去讀懂 token 怎麼驗。
328 行看起來比較少,但那 328 行你必須整段讀懂才敢動。704 行你只要讀懂其中一個檔案。
真正該衡量的不是總行數,是「為了改一件事,你必須理解多少」。

這是這篇最想講的一點,也是我後來才理解的。
舊系統的路由是後端驅動的:使用者登入後,後端回傳一份選單清單,前端拿到之後才即時產生對應的路由。
這個設計聽起來有點違反直覺(路由不是應該寫在前端嗎?),而且它的實作在舊系統裡確實很亂:轉換邏輯散在三個檔案,還用一個全域事件通知「選單載入完成了」(Day 4 講過那個假的 EventBus)。
所以重寫的時候,一個很自然的念頭是:順便把它改成前端寫死路由,用權限碼控制顯示就好。
我們沒有。
因為那個設計本身是對的,而且它已經長進了組織的流程裡:
如果前端改成寫死路由,那要換的就不只是程式碼,是後台維護介面、SOP、測試腳本、以及所有相關人員的習慣。
那已經遠遠超出「前端重構」的範圍了。而且說實話,換完之後好處是什麼? 對前端工程師來說可能比較直覺,但對整個組織來說,是拿一堆流程重做去換一點點程式碼的順眼。
所以我們做的是:保留同一個心智模型,只把實作寫清楚。
框架換了、寫法換了、檔案結構換了,但「後端決定選單、前端據此生成路由」這件事完全沒變。
重構不一定要換掉架構決策。很多時候原本的決策是對的,錯的只是它被實作的方式。
我覺得這是整個模組四最重要的一句話。因為重寫的時候,最大的誘惑就是「順便把架構也換成我喜歡的樣子」——而那通常是把個人品味當成技術改進。
拆守衛的過程中,我們踩到一個所有做過權限系統的人都會遇到的坑。
情境是這樣:
使用者的 token 過期了 → 守衛偵測到 → 導向登入頁
→ 但導向登入頁也會觸發守衛 → 守衛又偵測到沒有 token → 又導向登入頁
→ 無限迴圈
這個問題的解法很多人是在守衛裡加 if (to.path === '/login') return next()。這樣可以擋住,但它只處理了「已經在登入頁」這個狀態,處理不了「剛剛才因為登出被踢過來」的情況。
新系統的做法是加一個明確的旗標:
// 登出的時候,寫一個標記
sessionStorage.setItem(LOGOUT_REDIRECT_FLAG, '1')
// 守衛裡偵測到這個標記,就知道「這次跳轉是預期中的」,
// 處理完之後把標記清掉
if (sessionStorage.getItem(LOGOUT_REDIRECT_FLAG) === '1') {
sessionStorage.removeItem(LOGOUT_REDIRECT_FLAG)
// ...放行
}
差別在哪?
if 判斷路徑:推測現在的狀態而且程式碼裡還留了一段註解,說明這個旗標是幹嘛的,這比旗標本身更重要,因為半年後看到 sessionStorage.removeItem(...) 的人,不會知道那在防什麼。
有沒有覺得這個模式很耳熟?這跟 Day 8 講的「把事件改成狀態」是同一件事:把「你要推測」的東西,換成「它寫在那裡」。
一、九個守衛的執行順序變成一個新的知識點。 舊系統只有一個函式,順序就是由上到下讀。現在你得知道「權限守衛在進度條之後」,而這個順序只寫在註冊那個檔案裡。如果有人不小心調換,可能會出現「進度條跑完了才發現沒權限」這種怪現象。
二、行數確實變多了,而這在某些場合很難解釋。 如果有人拿「程式碼行數」當重構成效指標,你會被質疑。我的建議是主動換掉那個指標:改成「改一個功能平均要動幾個檔案」或「新人理解這段要多久」。
三、保留舊的心智模型,代表你也繼承了它的限制。 後端驅動路由有個先天問題:前端在拿到選單之前,什麼都不能做。所以首次載入一定有一段等待。我們沒有解決這件事,只是接受它。
明天 Day 18 講權限,而這篇的主軸是被實測推翻的。我原本以為舊系統只有「能不能進這一頁」的權限——結果它早就有按鈕級權限了,而且有兩套並存,其中一套用在 42 個檔案、另一套用在 133 個,還有 20 個檔案兩套都在用。