iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Modern Web

一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年系列 第 17

Day 17|重寫完,路由的程式碼從 328 行變成 704 行

  • 分享至 

  • xImage
  •  

模組四|核心系統改寫(Day 17–21)

先給一個看起來很糟的數字。

舊系統負責「路由與選單」的程式碼,散在四個檔案裡,合計 328 行。
新系統重寫之後,同樣的職責變成 704 行。

兩倍以上。

如果你的重構 KPI 是「減少程式碼」,這一篇會很難交代。但我想論證的是:這個變化是對的,而且我們刻意讓它發生

舊系統:一個守衛做六件事

Vue Router 有個機制叫「導航守衛」:每次使用者切換頁面之前,先跑一段程式碼。你可以在這裡檢查登入狀態、擋掉沒權限的頁面。

舊系統只用了一個守衛。而那個守衛裡面,做了這些事:

它在做什麼 屬於什麼職責
啟動頁面頂端的進度條 視覺回饋
檢查有沒有 token 認證
判斷是不是鎖屏狀態,是的話導去鎖屏頁 認證
把這一頁加進上方的多頁籤 版面狀態
沒有 token 就導去登入頁 認證
設定瀏覽器分頁標題 視覺回饋

六件事,三種完全不同的職責,擠在同一個函式裡。

這正是 Day 4 講過的那把七合一工具,只是換了個位置:六種職責長在同一個握柄上,你想修其中一個,其他五個都要一起停用。

而路由守衛比那把工具更麻煩的地方在於——它每一次頁面切換都會跑。所以任何一段出錯,影響的不是某個功能,是全站。

實務上這造成什麼?Day 3 那個真實案例就是:有人為了修「切回舊分頁時搜尋條件消失」,改動了多頁籤那一段。而那段程式碼跟認證邏輯寫在同一個函式裡,所以改它的人必須先讀懂認證,才敢動手。

新系統:九個窗口

我把那本用鐵圈裝訂死的厚本子剪開,只抽出需要的那一頁來讀

新系統把這件事拆成九個獨立的守衛,每個只做一件事:

頁面狀態 → 載入狀態 → 取消未完成的請求 → 捲動位置
→ 訊息提示 → 進度條 → 權限 → 頁面狀態收尾 → 參數選單

它們被統一註冊在一個地方,順序明確。

行數變多,就是變在這裡。 九個檔案各自有 import、有型別、有函式簽名,這些是「拆開」的固定成本。

那為什麼值得?

因為現在「改進度條」和「改權限」是兩個檔案的事。你不需要為了調整一個載入動畫,去讀懂 token 怎麼驗。

328 行看起來比較少,但那 328 行你必須整段讀懂才敢動。704 行你只要讀懂其中一個檔案。

真正該衡量的不是總行數,是「為了改一件事,你必須理解多少」

但心智模型,我們一個字都沒改

我把那張破舊地圖上的路線,一條不差地描到乾淨的新紙上

這是這篇最想講的一點,也是我後來才理解的。

舊系統的路由是後端驅動的:使用者登入後,後端回傳一份選單清單,前端拿到之後才即時產生對應的路由。

這個設計聽起來有點違反直覺(路由不是應該寫在前端嗎?),而且它的實作在舊系統裡確實很亂:轉換邏輯散在三個檔案,還用一個全域事件通知「選單載入完成了」(Day 4 講過那個假的 EventBus)。

所以重寫的時候,一個很自然的念頭是:順便把它改成前端寫死路由,用權限碼控制顯示就好。

我們沒有。

為什麼不改

因為那個設計本身是對的,而且它已經長進了組織的流程裡:

  • 後台有專人在維護那份選單資料
  • 新增一個頁面的權限,有既定的 SOP
  • 測試人員有一套對應的檢查流程

如果前端改成寫死路由,那要換的就不只是程式碼,是後台維護介面、SOP、測試腳本、以及所有相關人員的習慣。

那已經遠遠超出「前端重構」的範圍了。而且說實話,換完之後好處是什麼? 對前端工程師來說可能比較直覺,但對整個組織來說,是拿一堆流程重做去換一點點程式碼的順眼。

所以我們做的是:保留同一個心智模型,只把實作寫清楚。

  • 選單轉成路由的邏輯 → 抽成獨立的 helper,可以單獨測試
  • 「選單載入完成」的通知 → 從全域事件改成 store 的狀態(Day 8 講過)
  • 守衛 → 拆成九個

框架換了、寫法換了、檔案結構換了,但「後端決定選單、前端據此生成路由」這件事完全沒變。

重構不一定要換掉架構決策。很多時候原本的決策是對的,錯的只是它被實作的方式。

我覺得這是整個模組四最重要的一句話。因為重寫的時候,最大的誘惑就是「順便把架構也換成我喜歡的樣子」——而那通常是把個人品味當成技術改進。

一個很經典的坑:登入頁的無限迴圈

拆守衛的過程中,我們踩到一個所有做過權限系統的人都會遇到的坑。

情境是這樣:

使用者的 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 講的「把事件改成狀態」是同一件事:把「你要推測」的東西,換成「它寫在那裡」。

代價

一、九個守衛的執行順序變成一個新的知識點。 舊系統只有一個函式,順序就是由上到下讀。現在你得知道「權限守衛在進度條之後」,而這個順序只寫在註冊那個檔案裡。如果有人不小心調換,可能會出現「進度條跑完了才發現沒權限」這種怪現象。

二、行數確實變多了,而這在某些場合很難解釋。 如果有人拿「程式碼行數」當重構成效指標,你會被質疑。我的建議是主動換掉那個指標:改成「改一個功能平均要動幾個檔案」或「新人理解這段要多久」。

三、保留舊的心智模型,代表你也繼承了它的限制。 後端驅動路由有個先天問題:前端在拿到選單之前,什麼都不能做。所以首次載入一定有一段等待。我們沒有解決這件事,只是接受它。

帶走什麼

  1. 重構不一定要換掉架構決策。 先分辨「這個決策錯了」和「這個決策被實作得很爛」,後者比前者常見得多。
  2. 判斷一段程式碼好不好,不要看總行數,看「為了改一件事,你必須理解多少」。 拆開會讓行數變多,但讓理解範圍變小。
  3. 當一個技術決策已經長進組織流程,換掉它的成本要算進 SOP、測試、教育訓練,不能只算程式碼。
  4. 用明確的旗標取代狀態推測。 「這是一次預期中的跳轉」寫出來,比用路徑去猜可靠。
  5. 旗標要配註解。 防禦性的程式碼如果沒說明它在防什麼,下一個人會把它當成沒用的東西刪掉。

明天 Day 18 講權限,而這篇的主軸是被實測推翻的。我原本以為舊系統只有「能不能進這一頁」的權限——結果它早就有按鈕級權限了,而且有兩套並存,其中一套用在 42 個檔案、另一套用在 133 個,還有 20 個檔案兩套都在用。


上一篇
Day 16|每 100 個測試站抓到的問題,有 1 到 2 個會溜到正式環境
系列文
一個活了六年的 Vue 2 後台,升級到 Vue 3 的那兩年17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言