模組三|遷移執行法與驗收(Day 12–16)
一百個頁面要搬,第一個搬哪個?
我們做了兩次。而把兩次的第一天並排放在一起看的時候,我發現一件很有意思的事:它們幾乎是相反的。
先看數字:
| 第一個專案 | 第二個專案 | |
|---|---|---|
| 第一天的 commit 數 | 15 | 22 |
| 其中「業務模組初始化」 | 0 | 13 |
| 第一天之後 | 停了 12 天 | 隔天繼續 |
第一個專案的第一天,沒有碰任何業務功能。
第二個專案的第一天,一口氣把 13 個業務模組全部開好。
同一個團隊、同一套技術棧、相隔三個月。為什麼做法完全不同?
先看第一個專案的 commit 序列(順序沒調整,這就是真實紀錄):
Add README.md
init
feat: 登入元件
feat: 登入功能
feat: 路由改用 history 模式
feat: 刪除多餘檔案 ← 清掉框架範本
feat: axios 攔截器處理
feat: 刪除多餘程式碼
feat: API 多語系參數處理
ci: 增加 test 環境設定
fix: 設定檔提示錯誤處理
ci: test 環境變數設定
十五個 commit,沒有一個是業務頁面。

「先做登入」聽起來是廢話,不登入怎麼測其他頁面?
但它的價值不只是「能進去」。登入是唯一一個會同時逼你決定四件事的頁面:
| 做登入必須決定 | 而這件事會影響 |
|---|---|
| 路由要用哪種模式 | 全站的網址長相、後端要不要配合 |
| 請求層怎麼攔截 | 所有 API 的錯誤處理、token 怎麼帶 |
| 環境變數怎麼放 | 之後所有環境切換 |
| 登入狀態存在哪 | 狀態管理的第一個決定 |
登入不是一個頁面,是骨架的體檢。
而且它有個很棒的特性:它是唯一一個「做不完就什麼都不能做」的頁面。所以拿它當第一關,你不可能偷懶跳過任何一個決定。

這是我覺得最值得講的細節。
第一天結束在 5 月 1 日。下一個 commit 是 5 月 13 日。
中間十二天,沒有任何提交。
我不知道那十二天實際發生了什麼(可能有假期、可能在忙別的事),但從 5 月 13 日之後的 commit 內容可以反推出至少有一部分時間是在做決定:
feat: 左側選單新增對應 views、router 及語系檔
feat: 新增每個模組的語系檔資料夾
feat: 新增每個模組的 API 資料夾
feat: 語系檔按頁面模組拆分資料夾
feat: vite 設定新增元件自動按需引入
這一批 commit 沒有任何功能,全部是目錄結構和命名慣例。
也就是說:在鋪第二層之前,團隊先決定了「東西要放哪、怎麼命名」。
這件事如果不先做會怎樣?我猜大家都知道答案:每個人照自己的習慣放,等到第 30 個頁面才發現有四種目錄風格,那時候再統一,要動的就是 30 個頁面。
遷移初期的「停頓」不是浪費,它是在買後面的一致性。 而且這種停頓在 commit 紀錄上看起來很難看(好幾天沒進度),所以需要有人扛得住。
第三層才開始碰業務。而第一個被搬的頁面是統計報表。
這個選擇我認為是刻意的,理由有三個:
一、它是唯讀的。 搬錯了頂多顯示錯誤,不會寫壞任何資料。
二、但它涵蓋了 admin 系統最核心的整套樣板:搜尋表單 + 表格 + 分頁 + 匯出。這四樣加起來,大概佔了後台八成的頁面結構。
三、它有真實資料。 不是空殼頁,一打開就能看出「資料對不對、格式對不對、分頁行為對不對」。
換句話說:用風險最低的頁面,驗證覆蓋率最高的骨架。
如果第一個搬的是「修改租戶額度」這種有寫入行為的頁面,同樣能驗證骨架,但搬錯的代價完全不同。
之後才輪到有寫入行為的頁面(租戶列表、訂單詳情)。
三個月後,同一批人開始第二個專案。第一天的 commit 長這樣:
init
feat: 更新代理位址、標題
feat: 調整登入頁
feat: 取得最新選單權限表
feat: 更新舊系統所有語系資料 ← 整包搬過來
feat: 商品類型管理初始化
feat: 商品類型管理初始化 ← 同樣訊息出現 4 次
feat: 商品類型管理初始化
feat: 商品類型管理初始化
feat: 結算管理初始化
feat: 稽核管理初始化
feat: 訂單與報表初始化
feat: 權限管理初始化
feat: 租戶會員管理初始化
feat: 帳單初始化
feat: 內容管理初始化
feat: 參數配置初始化
feat: 網域管理初始化
feat: 更換選單圖示
22 個 commit,13 個是「某某管理初始化」。
而且沒有停頓,隔天就接著做權限判斷和 API 格式統一。
因為它不需要探索了。
你第一次煮一道沒做過的菜,會怎麼做?
開著食譜、一步一步來。切完洋蔥才想到蒜頭還沒剝,鍋子熱了才發現醬油在櫃子最裡面。整個過程停停走走。
第二次煮同一道菜呢?
你會先把所有材料切好、調味料擺出來,然後才開火。因為你已經知道整個流程會用到什麼。
專案 A 是第一次煮:邊做邊決定目錄怎麼放、命名怎麼取、共用元件長什麼樣。所以它必須一層一層長,中間還要停下來想。
專案 B 是第二次煮:慣例已經定了、共用元件已經有了、坑已經踩過了。所以最有效率的做法就是先把 13 個業務模組的空殼全部擺出來,再逐一填。
兩種做法都對,因為它們面對的不確定性不同。
專案 B 第二天有這樣一個 commit:
feat: 移除頁面功能按鈕的權限判斷(未來再加)
按鈕級的權限判斷,專案 A 早就做過了(Day 18 會專門講)。技術上完全知道怎麼做。
但專案 B 起步的時候刻意先關掉。
為什麼?因為權限判斷是滲透性的:它會出現在幾百個按鈕上。如果在「頁面都還沒搬完」的階段就掛上去,你會在每個頁面都多處理一件事,而且那件事跟「這頁能不能跑」無關。
先讓它跑起來,再讓它對。
這是一個很成熟的判斷:知道怎麼做,跟現在就該做,是兩件事。 而且他們把「未來再加」寫進了 commit 訊息,這比寫在某個人的腦袋裡好太多。
上面那串 commit 裡,「商品類型管理初始化」連續出現了四次。
即使是第二次做、即使已經有現成的參考,同一個模組還是反覆了四輪才定案。
我覺得這個細節比任何成功故事都真實。它說明:遷移期的反覆是常態,不是失誤。 如果你的計畫假設「每個模組搬一次就對」,那個計畫從第一週就會開始落後。
把兩個專案的做法抽象出來,我認為判準是這三條,而且有先後:
第一順位:不做就什麼都不能測的東西。
登入、路由、請求層、環境變數。這些沒有商量餘地,而且做它們的過程會逼你決定一大堆事。
第二順位:風險最低但覆蓋率最高的業務頁。
唯讀的報表頁是理想人選,搬錯不會寫壞資料,但能驗證整套樣板。這個階段的目的是「證明骨架能用」,不是「交付價值」。
第三順位:有寫入行為、業務規則複雜的頁面。
此時你已經摸熟新環境了,可以專心處理業務邏輯本身。
而遷移的單位是業務域,不是單一頁面,因為同一個域的頁面共用同一批 API 和資料模型。只搬其中一頁,你會發現它依賴的東西有一半還在舊系統裡。
一、「先做骨架」在外部看起來像沒有進度。 前兩週你交不出任何一個使用者看得到的畫面。這需要事先溝通,否則第三週就會有人來問「你們到底在做什麼」。
二、第二個專案的效率優勢,是用第一個專案的痛換來的。 不要看到專案 B 一天鋪 13 個模組就以為可以照抄,沒有專案 A 那三個月的探索,那 13 個空殼會全部長錯地方。
三、「未來再加」有機率變成「永遠不加」。 專案 B 那個被關掉的權限判斷後來確實補上了,但我們也有其他「未來再加」的東西到現在還沒動(Day 10 的嚴格型別檢查就是一個)。寫進 commit 訊息只是備忘,不是保證。
明天 Day 13 講整個重構裡最貴、也最少人談的一段:新舊兩套系統同時上線,共存了一年以上。我會給一個很刺眼的畫面:共存期間有一段時間,舊系統的開發量比新系統還多。