
我們這一年如何用 AI,把 E樂堂搬到下一個可維護的階段
有一頁後台功能,管理員試了 53 次,每次都拿到錯誤頁。
另一個統計頁,一開就把整頁拖垮。其中一支查詢實測要跑 7 分 23 秒(442,900 ms)。更麻煩的是,它不是慢慢等就會好,而是把資料庫工作行程佔住,讓其他原本只要 數百 ms 的查詢、甚至登入心跳,都一起排不到。
這不是單一 bug。這是一套跑了很多年的 SaaS 系統,技術債長到一定程度後,開始在產品體驗、資安風險、維運流程上同時冒煙。
E樂堂是一個多網校線上開課 SaaS。網校可以自行上架課程、管理學員、處理金流與影音,而每一個功能背後,都是一套不能停的系統。
所以我們面對的不是「把一個舊專案重寫掉」這麼單純,而是:一個仍在營運、仍在收錢、仍有學員資料進出的系統,要怎麼在不中斷服務的前提下,逐步搬到比較安全、可維護、可演進的狀態。
這篇文章,我想用產品經理的角度,記錄我們這一年實際做了什麼,以及 AI 在這件事裡真正扮演的角色。
舊系統到底有多大?
很多人聽到「技術債」,會先想到程式寫得不漂亮、架構不夠新。但真的進到現場,第一件事不是評論好壞,而是先知道它有多大、哪些地方不能亂動。
E樂堂這個產品家族有四棵程式碼樹:
| 區塊 | 規模 |
|---|---|
| 前台課程網站 | PHP 4,783 支檔,4.55 MB PHP |
| 網校後台 | PHP 4,777 支檔,4.79 MB PHP |
| 平台大後台 | 2.12 MB PHP |
| 共用核心/函式庫 | 22,491 支檔,20.52 MB PHP |
整體稽核下來,全家族共有 27,274 支 PHP 檔,其中第三方套件 16,339 支。類別依賴關係有 46,110 條,靜態分析可達的檔案 613 支,疑似無用檔案 9,313 支,其中高信心 6,563 支。
值得一提的是它的年紀:這個產品的第一筆程式碼紀錄是 2017 年,而它跑的執行環境 PHP 5.6 是 2014 年釋出的版本。也就是說,我們面對的是一套用十年前技術堆起來、但每天還在營運的系統。
資料庫也不是小系統。正式生產庫有 463 張表,資料 33.8 GB,外加索引 0.6 GB。
最大的表是學習紀錄 log,來到 9,318 萬列 / 7.7 GB。另外兩張瀏覽軌跡表,各約 517 萬列 / 2.5 GB。站內信件表也有 2.6 GB。
這代表什麼?
代表產品決策不能只問「要不要重構」。真正的問題是:哪一段技術債正在傷害使用者?哪一段會帶來資安風險?哪一段現在不處理,之後每一次改版都會被放大?
技術債不是一種債,而是四種債疊在一起
執行環境債:不是升級版本號而已
這套系統原本跑在 PHP 5.6。而 PHP 5.6 在 2018 年底 就停止安全支援。換句話說,一個每天處理交易與學員資料的系統,長時間跑在不再收到安全修補的環境上。
升級也不是「把版本號改掉」就好。PHP 8 之後,很多以前只是發出警告的寫法,會直接變成致命錯誤。
我們做了一次全量稽核,自動掃描 27,274 支檔案。光是需要人工判定的候選,就包含:
- 字串位移候選 12,647 筆
- 陣列內建函式候選 126 筆
- 字串函式候選 114 筆
- 動態屬性 176 筆
- 型別轉換 150 筆
json_decode26 筆
這些不是全部都是 bug,而是每一筆都需要判斷「真的會不會在真實路徑上爆掉」。
後來我們修掉其中一類舊函式呼叫問題,部署後實測,錯誤從 30 分鐘 14 次降到 0。
效能債:局部問題,會拖垮整頁
後台統計頁的問題很典型。
一開頁面,5 支排行榜同時發動,其中 2 支會對 2.5 GB 的表做全表掃描。實測單支查詢耗時:
| 查詢 | 修正前 |
|---|---|
| 逐日統計 | 3 分 20 秒(200,000 ms) |
| 熱門課程排行 | 1 分 33 秒(92,600 ms) |
| 活躍學員排行 | 7 分 23 秒(442,900 ms) |
使用者看到的是整頁空白。但根因不是整套系統都慢,而是表上只有主鍵索引。兩張 517 萬列的表,任何依「網校+時間」的查詢都必須全表掃描。
更值得注意的是,同一頁其他 16 支查詢只要 1–637 ms。也就是說,效能債有時候很局部,但它會用最壞的方式影響整體體驗。
安全債:不是被攻擊才叫資安事件
我們也發現網站根目錄直接指向程式碼倉庫根目錄,導致內部說明文件、稽核產出、主機差異資料可以被公開下載。
更嚴重的是,測試時期留下的檔案仍在線上。其中一個檔案內含第三方寄信服務 API key,另一個內含雲端測試資料庫帳密,任何人都能直接讀取。
權限管理也有問題:部署樹裡有 20,461 筆檔案權限是全開的 777,而且沒有規則。
後來我們把共用安全規則補到 8 個生產站台與測試機 3 個站台,移除 39 支稽核文件/測試頁,刪除 6 支含憑證或測試殘留的檔案,並從版控移除 56 支殘留的 sitemap 產物。也查過 access log,確認沒有外部讀取足跡。
流程債:沒有證據,就沒有真正完成
另一個問題是流程。
沒有自動化測試,沒有 CI/CD。部署靠人工把檔案傳上去,再靠人工比對。
結果就是:倉庫版本與機器上實際跑的版本不一致。實測發現,生產機的共用核心落後倉庫主線 28 個修正,其中包含已經修好但沒有上線的致命錯誤。
從產品管理角度,這其實很危險。因為團隊以為「已經修好了」,但使用者實際碰到的還是舊問題。
使用者最有感的那一段:首頁
技術債講起來很抽象,但使用者只在意一件事:首頁的課程卡片什麼時候出現。
而首頁一開始不只是慢,是壞的。PHP 8 升級初期,首頁區塊在 PHP 8 下直接觸發致命錯誤,整個區塊變成錯誤頁;後來在清理對外目錄的安全殘留檔時,又誤刪了課程圖卡唯一一行初始化,卡片整排不出來。這也讓我們的流程多了一條規則:刪檔前必須先做反向依賴掃描。
修好之後才開始追速度。量測方式是:在正式站、用真實瀏覽器(Playwright)打開大型網校 HOWTO好好學(howto.eletang.com.tw)的首頁,看 150 張課程卡片什麼時候出現。
第一輪,我們發現課程區塊的程式碼裡有一段「先轉圈、硬等 2 秒、才送出請求」的老保險,沒有任何功能性依賴。移除後:
| 正式站首頁事件 | 改前 | 改後 |
|---|---|---|
| banner / footer | 7.34 秒 | 5.78–6.17 秒 |
| 送出課程請求 | 8.24 秒 | 5.65–6.08 秒 |
| 課程卡片出現 | 10.01 秒 | 7.77–8.33 秒 |
更硬的證據是 A/B 對照:改前,請求是在「DOM 就緒後 1,324 毫秒」才送出;改後變成「−7 毫秒」(幾乎同一瞬間)。而卡片數量與區塊內容逐 byte 相同,沒有為了變快而少顯示任何東西。
第二輪處理的是「跑錯時間的工作」:第三方追蹤與廣告腳本原本在頁面解析期就開始下載,還有一支自動登入的同步請求會把主執行緒鎖住 401 毫秒。我們把外部腳本延後到頁面就緒後才載入(但事件照常送出,實測 GA4、Pixel、廣告都正常投遞),同步請求改成非同步。主執行緒的長工作合計從 1,950 毫秒降到 1,587 毫秒,最長一段從 559 毫秒降到 388 毫秒,同步請求歸零。
首頁的後端也被整理過:
- 每個請求要對同一批資料重複查詢,累積 1,956 支 SQL,只因為空結果沒有快取 → 加上請求內快取
- 有一條路徑只是為了比對課程 id,卻每次都重建整份清單,白付 0.34 秒與 30 支 SQL → 改成只取 id
- 每個請求都要確認訪客的 IP 國別,而同一個請求裡就查了 3 次、每次掃 7.6 萬列(約 100 毫秒 × 3),把 TTFB 吃掉 → 改成 1 次、0.0014 秒;頁面 TTFB 從 0.47 秒降到 0.14 秒,正式站部署後實測 0.185–0.199 秒
- CSS/JS 的版本號原本用「當下秒數」,所以每一頁都是全新網址、每次瀏覽都重下載 206 KB 與 88 KB → 改用檔案時間,瀏覽器終於可以快取
還有一個不是速度、但使用者一定感覺得到的修正:首頁原本會在載入 2 秒後強制把畫面捲回頂端。使用者往下滑、正在看圖卡載入的瞬間被拉走。這個行為現在取消了。
首頁還沒做完的部分我們也記著:頁面裡仍有約 221 KB 用不到的 plugin,以及約 397 KB 的主題 bundle 可以精簡。這是下一批。
AI 進場後,我們不是叫它重寫系統
這次的施工不是「把需求丟給 AI、讓它自己做完」。實際上,這件事是由三個角色分工完成的:
- 我(產品經理):定義問題與優先順序,決定哪些先修、哪些先不做,並承擔「這個改動能不能上線」的判斷。
- 我的 AI 工程助理(跑在 Hermes agent 上):把問題寫成 issue、審查 PR、合併、部署到測試機與正式站,並負責驗收——量測前後數據、比對檔案雜湊、查資料庫與錯誤日誌,再把證據寫回討論串。
- Codex(AI coding agent):負責實際寫程式。它讀完 issue 與規格後開 PR,等我這邊審過才合併。
我認為這種分工才是 AI 能不能真的落地的關鍵:不是找一個 AI 取代人,而是把「決定做什麼」「確認做得對不對」「實際動手做」切成三件事,每一件都有可驗證的產出。
在 2026-09-13 → 2026-09-26 這 14 天裡,四棵樹合計有 101 個 PR 合併上線:
| 區塊 | PR 數 |
|---|---|
| 前台 | 32 |
| 網校後台 | 30 |
| 平台大後台 | 5 |
| 共用核心 | 34 |
同一段期間關閉 53 個 issue。PHP 8 相容性相關 PR 包含前台 8 個、網校後台 10 個、大後台 3 個、共用核心 13 個。
這 14 天裡,實際跑完的工程工作包括:
- 對 27,274 支 PHP 檔做靜態掃描與依賴分析
- 產出可重現的稽核報告,每一筆候選都能追到行號
- 將四棵樹移植到 PHP 8.3 執行環境
- 新舊樹並存,先乾跑比對,不直接切掉退路
- 逐層修致命錯誤:先讓頁面 render,才看得到下一層問題
- 處理效能瓶頸與安全止血
- 產出驗收證據,而不是只說「看起來正常」
運作起來的樣子是:每張 issue 都對應一個 Slack 討論串;Codex 開 PR 後,我的 AI 工程助理每 30 分鐘自動掃一次並留下審查意見;我確認沒問題才合併、部署,部署後再把驗收數據貼回同一個討論串。
其中一次生產資料庫索引調整,我們用 INPLACE/LOCK=NONE 執行。期間每 3 秒打一次真實前台請求,共 37 個樣本全部正常,最慢 0.53 秒,沒有使用者受影響。
同樣的 AI 能力也用在蓋新東西上:跨平台的公開課程索引、客服知識庫、LINE 客服與搜課機器人,以及 30 個自動化排程工作(其中 16 個是各課程平台的資料爬蟲)。對產品經理來說,這代表 AI 的價值不只是「維護舊系統」,而是讓原本永遠排不進 roadmap 的事情,變成做得完的事情。
這裡的重點不是「AI 很神」。而是如果沒有 AI,這種量級的掃描、比對、修補、驗收,很容易因為人力成本太高而永遠排不上優先順序。
同一個方法,我們也拿來重整整份使用手冊
如果說改程式是「讓系統活下來」,手冊就是「讓使用者活得下去」。而手冊在大多數公司都排在最後:它不是營收功能,沒人會為它開一個 sprint。
我們的做法是把「稽核」這件事自動化:先把線上手冊全部抓下來(一次 160 頁,含頁面層級、修改日、字數、圖片數與每一層標題),再把後台的功能清單從程式碼裡抽出來 —— 不是憑印象列,而是直接把後台左選單(管理者、講師、平台管理員三種角色)與 78 支功能程式裡的全部動作抽出來,並且先剝掉註解,然後逐項拿功能名稱去手冊全文比對,命中 0 次就是完全沒寫。
比對出來的結果分成三類:完全沒寫的 15 項、有頁面但等於沒寫的(有一頁整頁只有 14 個字、1 張圖,實際上只有標題)、以及位置不對的。
補寫要照手冊原本的寫法:麵包屑式標題、逐一說明每個欄位與每一顆按鈕、每個小節配全寬截圖。所以 AI 得先登入後台、逐頁截圖,再產出 14 篇新頁草稿與 4 篇既有頁的補充草稿。
真正花時間的不是寫,而是取捨與對齊:
- 手冊的「位置」必須對齊後台選單,不是按我們自己的歸類。有三頁被直接糾正(管理者發信要掛在「站內訊息」底下、Instagram 要掛在「社群設定」、購物車折扣要掛在「促銷管理」)。
- 後台有一個獨立的「分潤」群組,手冊完全沒有這個章節。做法不是併進鄰近章節,而是補一個章節出來:建一個容器頁、把後面的章節序號全部往後挪、再把既有頁搬進去 —— 還要先確認舊網址會自動轉址,外部連結不會斷。
- 改任何既有頁之前,先存一份原文快照,交付時附上「改寫前後對照」,避免改了什麼沒人知道。
- 有一頁 AI 原本寫成「這個功能疑似壞掉/待確認」。結果被我發現打回去:那是給客戶掃描用的客服 LINE@ QR Code。查證後整頁改寫。這件事的教訓是:在還沒查清楚之前,不要把猜測寫成文件。
結果是 18 頁在同一天上線(14 篇新頁 + 1 個新章節 + 既有頁補寫與搬移),連同 18 張後台截圖一起進到手冊站。手冊從 160 頁變成 175 頁。
驗收也不是「API 回 201 就收工」:逐頁開線上頁面確認狀態碼、確認頁面真的出現該頁的關鍵字、確認圖片真的載入(不是被延遲載入騙過去)、確認側邊欄位置正確、確認舊網址轉址正常。
老實說,手冊還沒補完。有些章節結構是錯的(同一個功能在三個章節各寫一份)、有些頁面仍然太薄。但至少現在它是一份有名有姓的清單,而不是一團霧。
Before / After:我們怎麼確認真的有變好?
我不太相信「感覺有改善」。尤其是舊系統,最怕修了一個地方,另一個地方壞掉。
所以這次每個項目都盡量留下可查證的證據:前後基線、逐檔 sha256 比對、容器日誌錯誤數、實際耗時、逐格輸出比對。
幾個比較有代表性的結果:
| 項目 | Before | After |
|---|---|---|
| 逐日統計 | 3 分 20 秒(200,000 ms) | 1 ms |
| 熱門課程排行 | 1 分 33 秒(92,600 ms) | 4 ms |
| 活躍學員排行 | 7 分 23 秒(442,900 ms) | 3 ms |
資料內容逐筆相同,只是不再全表掃描。
其他結果也很具體:
- 執行環境從 PHP 5.6 搬到 PHP 8.3.6
- 首頁(HOWTO好好學)從 10.01 秒降至約 6.2 秒
- 某個後台功能頁從「管理員試 53 次全拿錯誤頁」變成正常開啟
- 內部錯誤日誌特定錯誤從 30 分鐘 14 次變成 0
- 容器 10 分鐘內 Fatal/Warning 0 筆
- 8 個站台原本 0 條共用安全規則,改為一套規則套用到 11 個站台
- 6 支含憑證或測試殘留的檔案從線上移除
- 機器落後倉庫 28 個修正的狀態已對齊
- 14 天內 101 個 PR 上線
搬到新執行環境後,我們也實測首頁 754 KB,連續 12 次回應一致,access log 0 個 4xx/5xx。舊樹仍保留可隨時切回。
當然也不是沒有踩坑。一次真實教訓是回滾腳本用了力道過猛的做法,把機器上必要的本地設定一起清掉。後來我們把策略改成「失敗即中止,不動樹」。這種教訓很實際,也比漂亮的流程圖重要。
三個 PM 觀察
觀察一:不要先問要不要重寫,先問哪裡正在流血
老系統最容易陷入兩種極端:一種是全部不要碰,因為怕壞;另一種是全部重寫,因為看不順眼。
但真實世界裡,系統還在營運,網校還在使用,訂單還在進來。產品經理要先找出「正在傷害使用者、正在放大風險」的地方。
這次我們先處理執行環境、安全暴露、後台致命錯誤、排行榜查詢。不是因為其他地方不重要,而是這些問題同時影響體驗、資安與維運信任。
觀察二:AI 可以放大量級,但不能替你負責取捨
AI 很適合做大規模掃描、跨檔案修補、重複驗證、產出候選清單。它讓原本靠人力很難完成的工作,變成可以排進節奏裡。
但 AI 不會自動知道商業優先順序。它也不會替你承擔「這個改動能不能上線」的責任。
哪些先修、哪些先不做、哪裡要留退路、什麼證據才算驗收通過,這些仍然是人的工作。
觀察三:驗收不是最後一步,而是整個遷移的設計核心
這次最有價值的不是合併了多少 PR,而是每一個改動都盡量留下證據。
效能改善要有前後耗時。相容性修正要看錯誤日誌。安全止血要查線上狀態與 access log。部署要比對機器版本與倉庫版本。切換環境要有連續請求、狀態碼、回應內容。
老系統不可能一夕之間變乾淨。後台部分頁面仍在逐一實測,已修好的相容性問題也是靠真實使用者路徑一層層撞出來,還沒走完。
但只要每次改動都有證據、每個風險都有退路、每個完成都有驗收標準,技術債就不再是一團霧。
它會變成一張清單,一段節奏,和一個可以被持續搬動的產品現場。