從零開始打造 Claude Code 工程團隊(三):主線後段——Reviewer、QA、Designer
Engineer 說「寫好了」,誰來相信他?Reviewer 兩段式審查、QA 邊界值掃描、Designer 旁支介入時機——主線後段三角色完整解析。
系列第三篇。Part 2 我們把忘記密碼功能跑到了 Engineer 完成 Phase 1。這一篇我們接力:Reviewer 怎麼接、QA 怎麼測、Designer 在什麼情況下才要出場。
開場:Engineer 說「寫好了」,誰來相信他?
延續上一篇的範例。Engineer 完成了 Phase 1:
- 前端
/forgot頁 - 後端
POST /api/forgotendpoint - 寄出 jwt token email
他在 handoff 文件寫:「全部測試通過,可以 review。」
新手會直接信。但資深做法是:Engineer 自己跑的測試只是必要條件,不是充分條件。為什麼?
- Engineer 寫測試時有確認偏誤——他會測自己「直覺會壞」的地方,但 bug 通常住在他直覺不到的地方。
- Engineer 容易漏掉 plan 對齊——code 跑得起來不代表它做的是 ticket 要的。
- Engineer 不會主動掃結構性問題——重複代碼、共用元件不一致、跨檔案的 config 漂移。
這就是 Reviewer 與 QA 存在的意義。
角色四:Reviewer——兩段式審 code
Reviewer 不是隨便看一遍。它有兩個明確分階段。
Stage 1:breadth scan(廣度掃描)
第一階段檢查的是通用品質問題,跟 ticket 內容無關:
- 有沒有 lint error / type error?
- 命名一致嗎?
- 有沒有重複的 code(structural chrome)?
- Git status 乾淨嗎,有沒有不該被 commit 的檔案?
- CJK 字串有沒有出現在不該出現的位置(例如 code identifier、function name)?
新手看到這些檢查會覺得「不就是 lint 嗎」。但這些在 LLM 的世界裡格外重要,因為LLM 寫 code 時容易產生看似合理的微小漂移:
- 把
fetchUser改成fetchUserData,但其他地方還叫舊名字。 - 加註解時留下中文,但這個專案規定 code 全英文。
- 改 component 時複製貼上,留下重複 80% 的 sibling component。
Stage 1 用機械化的工具檢查(grep、tsc、自動掃描),人不需要太花腦力。
Stage 2:depth alignment(深度對齊)
第二階段才是 review 真正的價值:對齊 plan。
Engineer 寫的 code,每一段是不是對應到 Architect 的 design doc 裡的某條目?
| Design doc 條目 | Engineer 對應 commit | 對齊? |
|---|---|---|
| §3.1 Token schema:30 分鐘 expiry | auth/token.ts 用 30 * 60 | ✅ |
| §3.2 SendGrid client 抽 interface | infra/email.ts 直接 import 真 SDK | ❌ |
| §4.1 失敗一律回成功(防列舉) | /forgot controller 區分回應 | ❌ |
§3.2 的問題是測試 seam 沒做出來——上線後才發現 unit test 跑不過,要花一倍時間補。
§4.1 的問題是安全性偏離設計——上線後可能被黑客當帳號列舉攻擊用。
這兩個問題 Stage 1 抓不到。Reviewer 要把 design doc 跟 git diff 並排看,逐條對齊。
Reviewer 的 Refusal Clause
最重要的一條:Reviewer 自己發現問題時,不能自己改。
新手 Reviewer 看到 /forgot 區分了回應,會手動把它改成「一律回成功」然後 commit。這是嚴重違規。為什麼?
- Engineer 不會學到——下次還是會犯同樣錯誤。
- 改動沒有經過 Engineer 重新測試。
- Reviewer 變成另一個 Engineer,失去獨立視角。
正確做法:寫 review comment,退回給 Engineer 改。然後 Engineer 改完再來一次 review。
Reviewer Never Do
- 不自己改 code(要退回給 Engineer)
- 不寫到 canonical(主分支)路徑——只能 review 別人的 PR,不能自己直接動 main
- 不放行未對齊 plan 的 commit(即使 code 看起來合理)
角色五:QA——邊界值才是 bug 的家
Reviewer 通過後,QA 上場。新手最容易把 QA 等於「跑一遍 happy path 看會不會壞」,但真正的 QA 工作 90% 在邊界值。
Boundary Condition Mandatory Sweep
QA 看到一個 input field(例如「輸入 email」)時,要強制掃過所有邊界:
| 邊界類別 | 範例 |
|---|---|
| 空值 | 沒輸入直接送出、輸入單個空格、\n 換行 |
| 長度極限 | 1 char、255 char、256 char、10000 char |
| 格式邊界 | @example.com(缺 local part)、a@、a@b(缺 TLD)、IDN domain |
| 編碼邊界 | unicode、emoji、零寬字元、RTL 字元 |
| 時序邊界 | 連續送 100 次、剛好在 token 過期前 1 秒重試 |
| 狀態邊界 | 同時兩個分頁送出、token 已用過再用一次 |
Engineer 寫測試時會測「正常 email + 不存在 email」兩個 case 就交差。QA 要強制把上面 6 類都跑一遍。
有沒有可能 6 類都正常? 大部分情況下,至少一兩類會出錯。例如:
- 長度 256 char 的 email 在 DB schema 是 VARCHAR(255) → 直接 500
- emoji email 沒做 normalization → token 簽名失敗
- 同時兩個分頁送出 → race condition,產生兩個 token,舊 token 沒失效
這些邊界新手 PM 跟 Architect 想不到,Engineer 不會主動測,QA 要當守門員。
Early Consultation——QA 在 Architect 之前就要出聲
進階做法:QA 在 Architect 還沒寫完設計時就先諮詢一次。為什麼?
因為有些設計選擇從根本上難測,等 Architect 寫完才發現要花一倍時間補測試 seam。例如:
- Architect 選方案 C(簡訊 OTP),但 QA 環境沒簡訊 mock infrastructure → 早講 Architect 可以選 B 避免。
- Architect 選 Redis 撤銷清單,但 CI 環境沒有 Redis → QA 要先講「測試環境怎麼處理」。
PM 在 release Architect 之前,會檢查 ticket 上是不是有「qa-early-consultation 已完成」的標記。沒有不放行。這個欄位是個強制 gate。
Interception Protocol——退件而不是補丁
QA 發現 Engineer 漏測一個邊界時,不是自己補 case,而是退件給 Engineer。理由跟 Reviewer 一樣:
- Engineer 才會學到。
- 補 case 應該由 Engineer 重新確認 code 行為符合 spec。
- QA 補 case = QA 變成 Engineer,失去獨立性。
QA Never Do
- 不挑戰需求合理性(那是 PM 的工作)
- 不自己補測試代替 Engineer
- 不跳過 boundary sweep 直接放行
Cross-Page Shared-Component Consistency
主流程跑完之後,QA 還有一個容易被新手漏掉的工作:檢查跨頁面共用元件的一致性。
範例:忘記密碼功能加完之後,登入頁底部的 footer 看起來怪怪的——你發現 Engineer 為了 /forgot 頁加了一個 footer 變體,把 <Footer> 改成可接受 variant prop。但他只有改 /forgot 頁,其他用到 <Footer> 的頁面(首頁、關於頁、文章頁)都還是舊版。
// Engineer 改的 /forgot 頁
<Footer variant="minimal" />
// 其他頁面(沒被 Engineer 動到)
<Footer /> // 用 default variant
問題:default variant 可能因為 prop 介面改變而靜悄悄壞掉——例如預設 logo 沒了、間距跑掉了。
QA 的工作是全站快速跑一遍,把所有用到該共用元件的頁面截圖比對。新手很容易只測「自己這個 ticket 動到的頁面」,這就是漏網的源頭。
角色六:Designer——什麼時候才需要叫設計師
主線到這裡跑完了。但有一類 ticket 還需要 Designer:動到視覺的 ticket。
Visual-Delta 欄位決定要不要叫 Designer
每個 ticket 在 PM 階段都會標一個欄位:
visual-delta: yes # 或 no
什麼情況算 visual-delta: yes?
| 情況 | visual-delta |
|---|---|
| 改 NavBar 順序、加新 menu item | yes |
| 加新 modal、新表單頁 | yes |
| 改顏色 token、字型大小 | yes |
| 後端 API 改回應格式 | no |
| 修純邏輯 bug(前端不改 UI) | no |
| 文件 PR、retro PR | no |
yes 的 ticket 在 Architect 階段就會 release Designer 平行作業。no 的 ticket 完全不要拉 Designer 進來——這是新手常犯的錯,把 Designer 當「QA Plus」用,浪費他的時間又稀釋他的權威。
Pencil SSOT——所有人讀同一個視覺真相
Designer 用 Pencil(或 Figma 之類的視覺工具)做設計。但設計檔不能只在 Designer 腦袋裡——要能被其他角色讀到。
做法是「single source of truth (SSOT)」:
Designer 在 Pencil 裡改設計
↓
匯出 JSON snapshot(規格) + PNG screenshot(視覺)
↓
commit 到 design_handoff/<ticket-id>/ 資料夾
↓
Engineer / Reviewer / QA 從這個資料夾讀規格做事
新手做法是 Designer 截圖丟到 chat 群裡,Engineer 自己對著截圖寫 CSS。問題:截圖會過期,但沒人通知。Designer 改了一版顏色,但 Engineer 還在看舊截圖,最後上線跟設計不同步。
Pencil SSOT 的好處:Designer 改設計 = 改 source 檔 = 自動更新 snapshot 與 screenshot。Engineer 每次動工前去拉最新的 design_handoff,永遠是同步狀態。
Touched-Frames Rule——只匯出被改的 frame
Pencil 一個檔案可能有 50 個 frame(首頁、登入頁、設定頁、彈窗 A、彈窗 B...)。Designer 改了 NavBar 之後,不能把整個 50 個 frame 全部 re-export。
為什麼?
- PR diff 會爆炸,看不出真的改了什麼。
- 沒改的 frame 也會 churn(因為 export 過程可能影響元素位置一兩個 pixel)。
- Reviewer 看 PR 時無法快速判斷影響範圍。
Touched-Frames Rule:只 export 被這個 ticket 改動到的 frame。NavBar 改了 → 只 export 含 NavBar 的 frames(首頁、設定頁、文章頁);彈窗 A 沒動 → 不 export。
AC ↔ Pencil Conflict Escalation
進到實作階段,Engineer 可能會發現:
「AC-2 寫『重設成功訊息要顯示 5 秒』,但 Pencil 設計檔上的訊息只有兩行字,5 秒太久了。」
這是規格與設計衝突。怎麼處理?
新手會自己選一個——「我覺得 3 秒就好」。錯。正確做法是:
- Engineer 停下來。
- 把衝突 escalate 給 PM。
- PM 仲裁:是 AC 要改成 3 秒,還是 Pencil 要改成顯示 5 秒?
- 一邊改完,PM 通知 Engineer 繼續。
這個流程看起來慢,但避免了 AC 跟視覺的長期 drift。一個 ticket 妥協一次沒事,三十個 ticket 各妥協一次,整個系統就是規格與視覺各自一套真相。
Designer Never Do
- 不主動介入 visual-delta: no 的 ticket
- 不 export 整個檔案(要遵守 touched-frames)
- 不私下解決 AC ↔ Pencil 衝突(要 escalate)
完整 ticket 接力示範(接續 Part 2)
Engineer 完成 Phase 1
↓
Reviewer
- Stage 1 breadth scan:lint OK,但發現一個重複 helper
- Stage 2 plan 對齊:發現 §3.2 SendGrid 沒抽 interface
- 退件給 Engineer
↓
Engineer 修兩個問題
↓
Reviewer 第二輪通過
↓
QA
- Boundary Sweep:email field 跑 6 類邊界
- 發現 256 char email 直接 500
- 退件給 Engineer
↓
Engineer 修 DB schema
↓
QA 第二輪通過
↓
Cross-Page Consistency 檢查:登入頁 footer 沒漂移,OK
↓
PM Phase Gate:AC-1 ✅ AC-2 ✅,放行 Phase 2
如果這個 ticket 是 visual-delta: yes(例如改 NavBar),時間軸會多一條:
PM 標 visual-delta: yes
↓
Architect 跟 Designer 平行啟動
↓
Designer 在 Pencil 改 NavBar,export touched frames
↓
Engineer 動手前先讀 design_handoff/<ticket-id>/ 取得規格
↓
過程中 Engineer 發現 AC vs Pencil 衝突 → escalate PM
↓
PM 仲裁,通知雙方繼續
小結
| 角色 | 核心職責 | 最容易違規的事 |
|---|---|---|
| Reviewer | 兩段式審查(breadth + depth) | 自己改 code 不退件 |
| QA | Boundary sweep + cross-page consistency | 只跑 happy path、自己補 case |
| Designer | Pencil SSOT + touched frames + 衝突 escalation | export 整個檔、私下處理 AC 衝突 |
下一篇我們離開 agent 層,進到 Skill 與 Hook:怎麼把重複流程封裝成可呼叫的 SOP,怎麼用 shell script 強制執行不可商量的規則。