免費

3000 行一次上線——複雜功能為什麼必須先 plan

面對複雜功能,本能反應是「先寫點試試」——但架構決策藏在第 3 個模型、第 5 個 edge case 裡。plan mode 的真正價值是把架構級對話前置到文字層面。用 Pickful 的社群陪審系統實戰:一個 commit 3032 行 119 spec 全綠,之後十幾個提交零架構回爐。


面對複雜功能,大多數人本能反應是「先寫點試試」。

問題是複雜系統有一個特點:架構決策藏在第 3 個模型、第 5 個 edge case、第 8 個積分規則裡。你一頭扎進程式碼,撞到這些決策時回頭改,比一開始在文字層面討論清楚貴 10 倍。

我用 Claude Code 的 plan mode 做 Pickful 的 TopicReview 社群陪審系統時,驗證了這個不等式的極端版本:一個 commit,3032 行,119 spec 全綠,一次上線。之後十幾個提交沒有一次是架構回爐,全是參數調整、UI polish、邊界 case 修補。系統穩定運行到現在,是社群治理的核心。

這篇講為什麼複雜功能必須先 plan,plan 階段到底在做什麼,溝通到什麼程度算可以開始寫。

系統有多複雜

TopicReview 是社群投票決定是否移除低品質貼文的系統。一句話講完——但規格一層層展開:

  • 5 個狀態:open → voting → decided → appealed → closed
  • 3 種判決:remove / warn / keep
  • 2 個審判階段:初審由 12 位陪審員投票;申訴由 5 位 judge(從 top 20 積分使用者裡選)重審
  • 多維積分流:10 pt 暫扣、10 pt 申訴 stake、陪審員判對 +5、judge 判對 +10、舉報人判對 +3、申訴成功退 stake + bonus + 暫扣
  • 4 類定時任務:24h 初審投票視窗、24h 申訴視窗、24h 申訴審查視窗、remove 裁決延遲 24h 發陪審員積分(因為申訴可能反轉)
  • 並行 + 回退:provisional removal 中投票反轉要 restore 貼文 + 退暫扣;申訴推翻要退 stake + bonus + 可能退暫扣 + 陪審員按新裁決重算

複雜性核心不在單條規則,是規則之間的交互。每加一條都可能觸發另一條的回退。

不用 plan 會撞哪幾種牆

直接寫,最容易出問題的不是看得見的事實——是你想不到要問的問題。從 TopicReview 的程式碼裡反推,至少四處不先 plan 就必然撞牆:

陪審員資格規則。看起來就是 User.jurors_and_judges.sample(12)。但實際規則是:排除原作者、排除舉報人、排除已投過初審的(防止同人投申訴)——三條排除疊加。合到一處寫 model 時容易漏一兩條。

積分延遲發放。Remove 裁決正常會觸發陪審員積分發放。但申訴能推翻 remove——一推翻陪審員站隊就反了,積分要按新裁決重算。所以 remove 裁決必須等 24h 申訴視窗過了才發積分。這條規則不寫在紙上,你會先發積分後發現要退——退積分比延遲發麻煩十倍。

定時任務介面設計CloseTopicReviewJob 看著就是「結束一個 review」。實際它要處理三種場景:

# 初審投票視窗到期
CloseTopicReviewJob.set(wait_until: voting_ends_at).perform_later(review.id)
# Remove 裁決後延遲發陪審員積分
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, award_juror_points: true)
# 申訴視窗到期
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, appeal_id: appeal.id)

不先梳理,會先寫成單一簽名,實現到第二種場景時整個介面重設計。

最貴的那種牆:provisional removal 期間能否申訴。投票過半數 remove 時貼文立刻下架(provisional),但整個 review 還在 voting 狀態、沒 decide。使用者能不能申訴?

  • 能 → decided 和 appealed 狀態間的 transition 要擴
  • 不能 → 使用者體驗很差,貼文被下了還要等 24h 才能抗議
  • TopicReview 實際選擇:可以在 provisional 期間申訴,但要先走完 finalize → decided 再 open appeal

這個選擇會反向影響 open_appeal! 的參數校驗和狀態機邏輯。不先想清楚,寫到一半改 = 半個系統重寫。

plan 階段到底在做什麼

Claude Code 的 plan mode 是一個不允許寫程式碼的模式——Claude 可以讀倉庫、想方案、和你討論,但任何檔案修改都被 hard-block,直到你同意一個 plan 才解鎖。

這個機械約束就是 plan mode 的核心價值——強迫架構級對話發生在文字層面

plan 階段實際在做的幾件事:

1. 畫狀態機和角色。5 個狀態、4 種角色(陪審員 / judge / 發文人 / 舉報人)、每種角色能做什麼不能做什麼——全部落在幾行 markdown。幾行 vs 幾十個檔案,修改成本差兩個數量級。

2. 每種角色的使用者流程 walkthrough

  • 陪審員視角:收通知 → 開 review → 看貼文 + 理由 → 投票 + reasoning → 領積分
  • 發文人視角:收通知 → 看裁決 → 如果 remove 考慮申訴 → 押 stake → 等待
  • Judge 視角:收 top-20 產生的 appeal → 投票 → 領積分

每條走不通的地方立刻暴露:「陪審員能看到其他陪審員的 vote 嗎?」「發文人在 provisional removal 時能做啥?」

3. 邊界 case 審問。plan 階段不是設計「正常情況」,是特意問那些平時不想想的問題:

  • 投票截止時 0 票怎麼辦?(最終選:預設 keep)
  • 申訴截止時 0 票怎麼辦?(dismissed,stake 扣除)
  • Judge 自己舉報過這個 post 能投嗎?(不能,同排除規則)
  • 多個陪審員同時觸發 finalize 條件,競態會不會重複發積分?(加 juror_points_awarded 冪等欄位)

這些問題 95% 寫程式碼時不會自然浮現——plan 階段強迫你一個個過。

4. 積分台帳。積分系統複雜到光討論不夠,要真的畫表:每筆 point 流動寫明觸發條件 + 數額 + 回退路徑。台帳對齊後所有邊界 case(暫扣退、申訴退、bonus)都能對上帳。

溝通到什麼程度算可以開始寫

一個硬標準:

  • 能 walk through 每條路徑不卡殼——從開 review 到 closed 的每種排列組合(keep / remove / warn × 有申訴 / 無申訴 × provisional / 非 provisional)都能一路講下來
  • 邊界 case 都有明確行為——不是「這個再說」,是「0 票預設 keep」、「provisional 期間允許 appeal 但要先 finalize」
  • 沒有「哦這個還沒想」——每個你能問出來的問題都已經有答案

達到這個標準,寫程式碼就是把 plan 翻譯成 Ruby。

一氣呵成階段的樣子

這個系統的第一次 ship:

d162f1e Add community moderation system with jury/judge review and appeals
  63 files changed, 3032 insertions(+)
  119 specs, 0 failures

63 個檔案,3032 行,119 個 spec 全綠。一次上線

之後十幾個 commit 驗證了架構穩定:

Apply legacy penalty (1pt) for posts created before 2026-03-26
Fix topic review appeal bugs: window mismatch and verdict not updated
Add topic_removed status for posts removed by community review
Default to keep verdict when review expires with zero votes
Reduce provisional removal penalty to 1pt during trial period
Allow topic creator to withdraw active reviews
Add handled tab to jury dashboard, fix tabs styling

每條都是 tune / fix / polish / 加小功能。沒有一次是「回去重新設計狀態機」或「積分模型要改」。架構骨架全對。

這是 plan 真正的回報:讓執行不可中斷。沒有「等一下這條路徑怎麼處理」——plan 裡已經寫了。沒有「邊界 case 我沒想到」——plan 階段問過了。沒有「這個介面要重設計」——plan 裡已經梳理過。

連續幾個小時寫程式碼,只做一件事:把清晰的設計落到鍵盤。

什麼時候不該用 plan

不是所有任務都值得 plan:

  • 簡單 bug 修復——定位 + 修 + 加 regression test,不需要先討論
  • 機械重構——改名字、提取函式、移檔案,路徑唯一,plan 是多餘一步
  • 只有一條顯而易見的路——加一個 GET endpoint、一個 UI toggle,方案沒什麼可討論的
  • 探索性 prototype——你自己還不知道要什麼,先跑一個粗糙版比想清楚更重要

plan mode 的收益來自降低重寫成本。如果任務根本沒有重寫風險,plan 只是開銷。

結尾

「先想好再做」聽起來像性格建議。plan mode 不是這個意思。

plan mode 是把架構級對話發生在可以改 10 個字的地方,而不是必須改 30 個檔案的地方。

複雜功能下,「words are cheap, code is expensive」這個軟體行業老生常談的不等式要反過來用——不是鼓勵你先寫程式碼再說(那只在成本差距小的時候成立),是讓你利用這個差距,把貴的溝通前置。

3000 行一次上線的感覺非常爽。不是因為我寫得快,是因為 plan 把「寫」變成了純機械動作。