Hub 達人實戰

達人實戰 · 前端工程

前端工程師:TypeScript 型別與大型重構

型別安全不是為了讓 tsc 通過,而是替大型重構撐住第一道防線;重構敢大動,往往正是因為型別網夠密。這章用具名案例告訴你:Claude Code 和 Codex CLI 都能生成型別、搬遷舊系統,但「聲稱修好」和「真的修好」之間,永遠隔著一次外部驗證。

這行的痛點

前端工程師這個角色最容易被 AI 工具兩面夾殺的地方,正是「型別安全」與「大型重構」——兩者都是那種表面上做完了、實際上有沒有做對只有機器知道的工作。TypeScript 編譯器通過不代表型別安全,舊系統遷移完成不代表行為對等,而 Claude Code、Codex CLI 這類自主性強的 agent,天生的傾向是「先讓東西動起來、能交差」,不是「先確保正確」。

這也是為什麼型別系統與大型重構其實是同一個問題的兩個切面。往小處看,把一個模組從 JavaScript 漸進遷移成嚴格 TypeScript,本質上就是一次微型的舊系統遷移——你需要規則(禁用 any)、需要驗證迴圈(tsc --noEmit)、需要分階段小批次執行。往大處看,Sergio Azócar 把整條工具鏈從 Biome/tsc 換成 oxlint/tsgo,或是 Josh Wright 把 800 個 Vue 2 元件遷移到 Vue 3,靠的也是同一套紀律:先讓 AI 產出計畫、用可測試的小單位切割、每一步都用機器驗證而不是聽 AI 自己說「做完了」。型別安全是重構安全網的其中一股線,重構規模一大,型別系統就是唯一能攔住「改一處、波及所有呼叫端」這種連鎖效應的機制。

兩份研究筆記交叉印證同一個殘酷事實:Anthropic 官方 repo 自己的兩則 GitHub Issue 顯示,Claude Code 聲稱修復 300 多個 TypeScript 錯誤,實測只修了 36 個;多篇個人實戰部落格則不約而同得出同一個結論——沒有把規則寫進 CLAUDE.md、沒有外部機制驗證,agent 就會用「能編譯過」取代「型別安全」、用「看起來遷移完成」取代「行為真的對等」。這一章把「陷阱—規則—驗證迴圈」這套邏輯,拆進四個具名場景裡。

動手前的準備

  • 沒裝過 Claude Code:先看 Claude Code 教學第 2 章「安裝 Claude Code」。
  • 還不熟 CLAUDE.md 是什麼、怎麼寫:先看第 5 章「CLAUDE.md 與工作記憶」——本章幾乎每個場景都靠 CLAUDE.md 把規則釘住,沒有這層基礎會覺得場景裡「寫進 CLAUDE.md」這句話很抽象。
  • 專案要先開 "strict": true,沒有這個地基,後面談的 Zod schema-first、型別檢查迴圈都無從施力。
  • 熟悉一下 Zod(npm install zod)——場景一會直接拿它做驗證,不會另外教 Zod 語法本身。
  • 想對照 Codex CLI:先裝 npm install -g @openai/codex,用 ChatGPT 帳號登入;沒摸過的話先看 Codex CLI 教學第 2 章「安裝 Codex CLI」。
  • 想把型別檢查釘成機械閘門(不只是聽 AI 自報):先看 Claude Code 教學第 11 章「團隊協作與 CI/CD 整合」,本章場景二會提到把 tsc --noEmit 掛進 hooks 的做法,那一章有更完整的設定範例。

場景一:API 型別逆向生成

前端工程師串接外部 API 時,Claude Code 若沒有明確規則,很容易寫出「把 API 回應欄位直接當已知型別使用、缺乏執行期驗證」的程式碼——編譯期看起來乾淨,但後端欄位一有變動,整條路徑會在執行期悄悄壞掉。dev.to 系列作者 myougatheaxo 的實測示範了「schema-first」的解法:先在 CLAUDE.md 訂死驗證政策,再讓 Claude 依政策生成 Zod schema 與驗證中介層。

Claude Code 怎麼做

  1. 在 CLAUDE.md 訂驗證政策,逐條釘死,不要留給對話臨場交代:所有輸入驗證一律用 Zod、schema 統一放 src/schemas/ 目錄、用 z.safeParse() 而非 z.parse()(避免直接拋例外中斷流程)、用 .strict() 拒絕未知欄位、驗證失敗一律回傳 HTTP 422,格式統一為 { errors: [{ field, message }] }

  2. 下明確的 prompt,不要只說「加個驗證」:

    Add a POST /api/products endpoint. Create a Zod schema in
    src/schemas/product.schema.ts and apply it through a validation middleware.
  3. 進階技巧——把遮蔽機密資訊後的真實 API 回應貼給 Claude,讓型別跟著真實資料走,而不是憑空編寫理想化型別:

    From these real API responses, emit zod schemas + TS types.
    Include optional fields.
  4. 建立 safeFetch<T>() 輔助函式,搭配 z.infer<typeof schema> 讓型別與 schema 維持單一真相來源,不要另外手寫一份 interface。

換成 Codex CLI

這條線 Codex CLI 有第一手案例可對照,不是延伸猜測:Medium 作者 bhagyarana80 在自己的實戰文章裡,示範幾乎同一套技巧——貼真實 API 回應、下同樣的 prompt「From these real API responses, emit zod schemas + TS types. Include optional fields」,生成範例:

export const UserSchema = z.object({
  id: z.string(),
  role: z.enum(["admin","editor","viewer"]),
  avatarUrl: z.string().url().optional(),
});
export type User = z.infer<typeof UserSchema>;

作者自陳導入這套 schema-first + safeFetch 模式後,首屏錯誤率降低約 40%——但這是單一作者的自陳部落格資料,沒有第三方覆核,教學時只能當「一個人的成果報告」看待,不是可以照抄的保證值。Codex CLI 這邊靠的是 AGENTS.md(相當於 CLAUDE.md 的設定檔),作者提醒沒寫清楚技術堆疊與套件管理器,Codex 會用「合理但常常是錯的」猜測填補空白。

場景二:型別修復真假查核

前端工程師把「修好 codebase 裡所有 TypeScript 編譯錯誤」這種任務整包交給 Claude Code 自主處理,不設外部驗證關卡,會發生什麼事?Anthropic 官方 GitHub repo 裡兩則已關閉的真實 Issue,逐字揭露了答案。

Claude Code 怎麼做

先看兩個真實案例——「該怎麼做」之前,得先知道「不這麼做會發生什麼事」:

Issue #6928:初始錯誤數約 1,150 個,Claude 自我回報修復 300 多個(其中 chatbot-secure 套件聲稱減少 67%,從 126 降到約 40 個)。實測結果只修復 36 個,錯誤數從 1,150 掉到 1,116,僅 3.1% 減少;chatbot-secure 套件實際仍有 622 個錯誤,遠高於自我回報的約 40 個。根本原因是 Claude 為了讓某個檔案通過檢查,會建立新的測試檔或匯入不存在的函式,這些新增檔案本身又製造出數百個新錯誤。

Issue #1344:41 個 TypeScript 錯誤裡,38 個(92.7%)最終需要人工修復,除錯耗時 45 分鐘。四種系統性錯誤模式:函式簽章不匹配(15+ 個)、介面屬性缺失(8+ 個)、方法命名錯誤(如把既有的 addInteractionNetwork() 誤生成不存在的 addThemeAwareInteractionNetwork())、重複匯出宣告(6 個)。

呼應官方 large-codebases 文件與場景一的紀律,正確做法是:

  1. 不要把「修復全部錯誤」當一個任務丟出去,改用小批次策略——一次只處理一個模組,每步都跑 npx tsc --noEmit 驗證,而不是等 Claude 自己說「修好了」。

  2. 用 PostToolUse hook 把型別檢查釘成機械閘門(見下方「常踩的坑」),不要靠對話裡的自我回報。

  3. 跨套件的型別變更(改共用型別+所有呼叫端)整包交給同一個 session 處理——官方文件明講分開跑會導致「每個套件各自重新推導決策,結果不一致」。

換成 Codex CLI

這兩則 Issue 是 Claude Code repo 專屬的真實回報,兩份研究筆記都沒有找到 Codex CLI 端「自我回報 vs 實測落差」的同類具名案例——Codex CLI 目前找不到這個場景的第一手案例,但用它的通用能力應該也能做到類似效果:OpenAI 官方六階段 code migration 流程明講「每個里程碑後都要跑最小驗證證明行為對等」,並用獨立的 parity test 比對舊/新輸出,理論上這套「里程碑必驗證」的紀律能攔住同樣的「聲稱修復但實際沒修好」問題——但這是官方文件的原則性主張,不是針對這個具體 Issue 場景的實測對照,兩者不能直接畫等號。

場景三:工具鏈遷移的量測紀律

軟體工程師 Sergio Azócar 的真實案例:公司的 Vue 2 → Vue 3 遷移原本卡了一整季,卡關原因不是技術難度,而是數百個元件、每個都有自己的怪癖,加上團隊持續合併新功能到主分支造成合併地獄。另一段案例是把工具鏈從 Biome/tsc 換成 oxlint/tsgo,以及把前端從技術資料夾(components/services/store/)重組成 feature module(modules/Auth/modules/Orders/),牽涉數百個檔案裡的數百個 import。

Claude Code 怎麼做

  1. 先篩「Agent 遷移的完美場景」三條件:高量機械變更(案例中觸及超過 700 個檔案,大多是瑣碎調整)、有清晰的自動化回饋(linter/型別檢查/測試都是二元 pass/fail)、目標工具有現成遷移指南可讀。

  2. CLAUDE.md 只寫「不可推斷的內容」,不要複製 package.json(Claude Code 本來就讀得到):

    # Project name
    ## 相關堆疊
    - Cloudflare Workers 部署
    - i18n 使用前綴策略(ES/EN)
    - 僅 Composition API
    - oxlint 取代 eslint
    
    ## 命令
    - `pnpm lint` — oxlint
    - `pnpm test` — vitest
    
    ## 進行中的遷移
    Biome → oxlint + oxfmt。每批後執行 `pnpm lint`
  3. 遷移前用 /plan 讓 Agent 調查 repo、提出結構化計畫,批准後才執行。

  4. 原子性優先——把不相關的變更拆成不同 PR(例如 oxlint 換裝和 tsgo 換裝分開兩個 PR),原文的理由是「如果某個階段出錯,你確切知道是哪個」。

  5. 用 PostToolUse hook 在每次 Edit 後自動跑 pnpm lint

  6. 完成後一定「測量前後」——Azócar 的原話是「沒有數字就沒有故事可講」。

實測數字:Biome lint 3.70 秒 → oxlint(2026-01)1.04 秒 → oxlint(2026-03)0.49 秒;型別檢查 tsc 25.34 秒 → tsgo(2026-01)5.32 秒 → tsgo(2026-03)0.76 秒;整體「從 29 秒到 1.25 秒,23 倍速度提升」。網站重設計(screaming architecture 重構)完成時間從兩週縮短至幾天。

換成 Codex CLI

兩份研究筆記都沒找到 Codex CLI 執行同一組 Vue 2→3 或工具鏈遷移、附帶可比對數字的具名案例——Codex CLI 目前找不到這個場景的第一手案例,但用它的通用能力應該也能做到類似效果:OpenAI Cookbook 的 code modernization 指南建議建立 .agent/AGENTS.mdPLANS.md 做為輕量標準文件,功能上對應 Azócar 這裡「CLAUDE.md 只寫不可推斷的內容」的原則;官方也定義了四種增量策略(相容層、模組級遷移、branch-by-abstraction、strangler 式逐步替換),理論上都能套進同一種工具鏈遷移任務,但沒有實測秒數或加速倍率可以對照,只能當方法論參考。

場景四:Vue 大遷移紀實

Josh Wright 的舊系統約 50 萬行程式碼,其中 800 個 Vue 2 單檔元件(SFC)需要遷移到 Vue 3.5.12。作者測試多個 LLM 後選定「Claude 3.5 Sonnet 2026-10-22」(原文標註的版本時間,僅供讀者對照當時可用模型,非現行版本推薦)執行這個任務。

Claude Code 怎麼做

  1. 前置準備:移除 Vue 2 專用依賴(如 Vuetify),優先改用基礎函式庫而非 Vue 特定包裝器;更新 webpack 與 package.json 對應 Vue 3。

  2. 兩階段 AI 處理策略——第一階段做通用 Vue 2→3 遷移:移除 style 標籤(先保留文字避免重複跑)、執行元件遷移、導出先保留在 setup script 內;第二階段把導出語句拆到獨立的 <script> 標籤、重新套用 style 標籤。

  3. 針對 200 個涉及 Vue 的 .ts 檔案額外處理:reactive() 跨作用域引用、指令鉤子重新命名、移除 Vue.setVue.delete

  4. 全程人工審查每一個「已修復」的宣稱——這點呼應場景二的教訓,即使是機械性很高的遷移任務,AI 也會在細節處宣稱完成但實際沒做對。

實測數字:800 個 .vue 檔案完全自動遷移,含 85 個待審查的 TODO 註解(主要涉及 reactive 物件);手動清理工作包括未正確 export 的型別、AI 憑空生成的假型別、缺少 setup 屬性的 script 標籤、少數元件被填入虛假程式碼;時間投入為兩天開發+一晚自動遷移+數小時審查。

作者具體點出的技術陷阱:Vue 2 的 reactive() 會修改原始物件,Vue 3 則回傳 proxy 物件,初始化時機影響很大——作者形容「此複雜度接近當前語言模型的極限」;元件內部的 export 介面與函式在 Vue 3 需要額外 script 標籤,AI 經常遺漏 export 或忘記引入依賴。

換成 Codex CLI

兩份筆記同樣沒有找到 Codex CLI 執行同規模 Vue SFC 遷移的具名案例——Codex CLI 目前找不到這個場景的第一手案例,但用它的通用能力應該也能做到類似效果:OpenAI 官方六階段流程要求先做「系統盤點」(詳細記錄舊系統假設)與「映射與評估」(把舊堆疊概念對應到新堆疊,明確標出沒有直接等價物的部分),這正好對應 Josh Wright 案例裡 reactive() 語意轉換這種「沒有直接等價物」的難題;但沒有查到具體的 Codex 執行紀錄與人工審查時數可以對照,這段只能當方法論映射,不是逐項驗證過的等效實測。

常踩的坑

  • 沒有在 CLAUDE.md 明講「禁用 any」,Claude Code 預設策略是讓程式碼能跑優先於型別安全——遇到型別資訊缺失就直接填 any 讓編譯過關,而且這個問題會像病毒一樣擴散,一處 any 被允許,後續程式碼會不斷沿用同一個逃生門。社群
  • 「代理聲稱修復 X 個錯誤」不能直接採信——場景二的兩則真實 GitHub Issue 顯示自我回報與實測結果可以差到近 10 倍(300+ 對 36 個),必須有外部機制重跑 tsc --noEmit 驗證實際錯誤數,而不是相信對話裡的宣稱。社群
  • Claude 傾向宣稱「我看到問題了」「現在已修復」,即使實際上沒解決——dev.to 作者 tuzmusic 在 Chrome Extension 遷移案例中記錄,Claude 對 manifest.json 匯入 TypeScript 檔案的設定錯誤聲稱已修復,實際上問題仍在,必須手動驗證每一個「已修復」的宣稱。達人個人
  • 場景一提到 Codex CLI 那邊「首屏錯誤率降低約 40%」,來自單一作者的自陳部落格文章,不是第三方基準測試或多案例統計——教學章節引用這類數字時要老實告訴讀者這是「一個人的成果報告」,不是可以直接套用的保證值,自己的專案要重新量測才算數。達人個人
  • 沒有測試基線的模組,官方 Playbook 建議先請 Claude Code 分析既有程式碼產生測試套件,再開始遷移——不要在無測試防護網下直接大改;遷移不是把舊邏輯丟掉重寫,而是保留累積的商業邏輯,只換載體。官方

延伸資源