免费

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 把"写"变成了纯机械动作。