58.3 架构评审
一句话
评审不是“审批盖章”,而是在不可逆之前,用一群人的经验换一次纠错机会;没有决策与行动的评审,只是开会。
先破除四个误区
| 误区 | 现实 |
|---|---|
| “评审 = 审批” | 审批只回答“能不能做”;评审要回答“该不该这么做、有没有更合适的方案、失败怎么退” |
| “评审人越多越好” | 人越多越没人负责。3–5 个真正相关的人,比 20 个旁听者有效 |
| “实现完再评审” | 实现完只剩“改不改”的沉没成本;评审要在不可逆投入之前 |
| “评审靠权威拍板” | 权威可以拍板,但必须留下依据与代价;否则下次还会犯同样的错 |
评什么,不评什么
| 该评 | 理由 | 不评 | 放哪 |
|---|---|---|---|
| 难以逆转的决策(数据模型、分片键、运行时) | 改一次伤筋动骨 | 命名、目录结构 | 代码规范 |
| 跨团队接口/契约 | 多方依赖,改一处伤一片 | 单个函数的实现 | 代码评审 |
| 非功能指标(延迟、容量、可用性) | 决定架构形态 | 代码风格 | lint/格式化 |
| 数据安全与合规 | 事后补救成本最高 | 测试用例写法 | 测试评审 |
| 成本与运维复杂度 | 隐形成本在第二年爆发 | 局部性能微优化 | 性能剖析 |
| 迁移与回滚方案 | 决定能不能安全上线;与 58.4 演进路线图 的波次计划对齐 | UI 细节 | 设计评审 |
🔑 评审的准入条件不是“方案写完了”,而是**“再往下走就要花大钱/改不动了”**。越晚评审,纠错成本越高。
五种评审形式
| 形式 | 适用 | 时长 | 产出 |
|---|---|---|---|
| 设计评审会 | 新系统、重大改造 | 60 分钟 | 结论 + 行动项 |
| RFC(书面评审) | 跨团队、需要留痕 | 3–5 天评论期 | 批准/驳回 + 评论归档 |
| 架构委员会 | 公司级标准、平台选型(选型流程见 58.5 技术选型流程) | 30–60 分钟 | 标准/例外决定 |
| 异步评审 | 分布式团队、小改动 | 1–2 天 | 评论 + 结论 |
| 事后评审 | 已上线系统 | 60 分钟 | 改进项/债登记 |
形式服务于风险,不是反过来:一个内部工具的重构,不需要架构委员会。
流程:五步,有时限
① 提交:1 页摘要 + 设计文档 + 关键数据(NFR/成本/备选)
② 预读:评审人 3 个工作日内读完,标出必须回答的问题
③ 评审:60 分钟,先讲问题与约束,再讲方案,最后逐条过问题
④ 结论:通过 / 有条件通过 / 驳回,写清条件与负责人
⑤ 留痕:结论落到 ADR + 行动项进 backlog
时限建议:预读 3 个工作日、评审会 60 分钟、结论 2 个工作日内发出。没有时限的评审,会变成“等某个人有空”。
角色:四个人就够
| 角色 | 职责 | 不该做 |
|---|---|---|
| 作者 | 讲清问题、方案、代价;记录问题 | 辩护一切、拒绝替代方案 |
| 主持(chair) | 控节奏、保证每个关键问题被回答、收敛结论 | 替作者设计 |
| 评审人(3–5) | 从各自领域挑风险:数据、SRE、安全、成本 | 只挑格式/命名 |
| 记录 | 记问题、结论、行动项、owner | 参与辩论 |
主持人最好不是作者:作者容易陷入“解释”,主持人负责“推进”。
评审清单:十条
| # | 检查项 | 不合格的表现 |
|---|---|---|
| 1 | 问题与约束写清了吗? | 上来就讲方案 |
| 2 | 至少 2 个备选,且说清为什么不选? | 只有一个方案 |
| 3 | NFR 是数字吗(P99/吞吐/可用性)? | “性能更好”“高可用” |
| 4 | 数据模型与一致性说清了吗? | 只画了服务框图 |
| 5 | 失败模式与降级路径? | 假设依赖永不失败 |
| 6 | 回滚/迁移方案? | 上线即单程 |
| 7 | 安全与合规结论? | “后面再补” |
| 8 | 成本(三年 TCO)? | 只算机器,不算人力 |
| 9 | 运维复杂度与可观测性? | 没有指标/日志/告警设计 |
| 10 | 谁负责落地、什么时候验收? | 没有 owner 与里程碑 |
第 5 条 “失败模式” 不要用嘴过一遍:可用性专项评审直接跑一遍 FMEA,逐个部件假设故障并打分,见 35.10 FMEA (故障模式与影响分析)。
一个能跑的“评审就绪检查器”
评审会最怕两种材料:没数字和没备选。程序在排会之前先检查,不满足就不排会。
package main
import (
"fmt"
"os"
"strings"
)
type design struct {
title string
problem bool
nfrs map[string]string // 指标 -> 目标值(必须是数字)
alternatives int
decision bool
rollback bool
cost bool
security bool
owner string
migration bool
}
type finding struct {
level, item, detail string
}
func review(d design) []finding {
var fs []finding
add := func(level, item, detail string) { fs = append(fs, finding{level, item, detail}) }
if !d.problem {
add("FAIL", "问题定义", "没有说清要解决什么问题、影响谁")
}
quantified := 0
for _, v := range d.nfrs {
if strings.ContainsAny(v, "0123456789") {
quantified++
}
}
if quantified < 3 {
add("FAIL", "非功能指标", fmt.Sprintf("只有 %d 个带数字的指标(至少 3 个:延迟/吞吐/可用性)", quantified))
}
if d.alternatives < 2 {
add("FAIL", "备选方案", fmt.Sprintf("只有 %d 个备选(至少 2 个,否则不是选型是通报)", d.alternatives))
}
if !d.decision {
add("FAIL", "决策与后果", "没有写选了什么、放弃了什么、代价是什么")
}
if !d.rollback {
add("FAIL", "回滚方案", "没有回滚/降级路径;上线即单程")
}
if !d.cost {
add("WARN", "成本", "没有三年 TCO 或资源估算")
}
if !d.security {
add("WARN", "安全", "没有安全/合规评审结论")
}
if d.owner == "" {
add("WARN", "负责人", "没有明确 owner,评审后无人跟进")
}
if !d.migration {
add("WARN", "迁移/灰度", "没有迁移步骤与灰度策略")
}
return fs
}
func run(d design) bool {
fs := review(d)
fmt.Printf("== 评审就绪检查:%s ==\n", d.title)
if len(fs) == 0 {
fmt.Println(" PASS 全部检查通过,可以提交评审")
return true
}
fail := 0
for _, f := range fs {
fmt.Printf(" %-4s %-12s %s\n", f.level, f.item, f.detail)
if f.level == "FAIL" {
fail++
}
}
if fail > 0 {
fmt.Printf(" 结论:不满足评审条件(FAIL %d)——补齐后再排评审,别让评审会变成写作课\n\n", fail)
return false
}
fmt.Println(" 结论:可以提交评审(WARN 项在会上确认)")
fmt.Println()
return true
}
func main() {
good := design{
title: "订单服务读写分离",
problem: true,
nfrs: map[string]string{
"p99": "120ms", "qps": "8000", "availability": "99.95%",
},
alternatives: 3,
decision: true,
rollback: true,
cost: true,
security: true,
owner: "order-team",
migration: true,
}
weak := design{
title: "缓存换成 Redis Cluster",
problem: true,
nfrs: map[string]string{"qps": "更高"},
alternatives: 1,
decision: true,
rollback: false,
cost: false,
security: false,
owner: "",
migration: false,
}
okA := run(good)
okB := run(weak)
fmt.Printf("可以提交评审:A=%v,B=%v\n", okA, okB)
if !okA || !okB {
os.Exit(1)
}
}
本机运行结果(退出码 1):
== 评审就绪检查:订单服务读写分离 ==
PASS 全部检查通过,可以提交评审
== 评审就绪检查:缓存换成 Redis Cluster ==
FAIL 非功能指标 只有 0 个带数字的指标(至少 3 个:延迟/吞吐/可用性)
FAIL 备选方案 只有 1 个备选(至少 2 个,否则不是选型是通报)
FAIL 回滚方案 没有回滚/降级路径;上线即单程
WARN 成本 没有三年 TCO 或资源估算
WARN 安全 没有安全/合规评审结论
WARN 负责人 没有明确 owner,评审后无人跟进
WARN 迁移/灰度 没有迁移步骤与灰度策略
结论:不满足评审条件(FAIL 3)——补齐后再排评审,别让评审会变成写作课
可以提交评审:A=true,B=false
注意:检查器只保证“材料够评审”,不保证“方案对”。它挡掉的是浪费大家时间的会,不是替代判断。
结论怎么写:三种,都要可执行
| 结论 | 含义 | 必须写清 |
|---|---|---|
| 通过 | 可以按方案实施 | 生效条件(如“先补监控”) |
| 有条件通过 | 大方向可以,但必须先满足条件 | 每条条件 + owner + 截止时间 + 谁来验收 |
| 驳回 | 方案不可行 | 驳回理由 + 建议方向 + 何时可再提 |
“原则上同意”是最坏的结论:既没有授权,也没有约束。评审结束前,主持人必须把结论落成上面三种之一。
评审之后:ADR 与行动项
评审的产出不是会议纪要,而是:
- 一篇 ADR:记录背景、备选、决策、后果(见 58.1 ADR 实践)。
- 行动项清单:每条有 owner、截止时间、验收方式,进 backlog。
- 例外记录:如果决定“先欠着”,登记为技术债(见 58.2 技术债管理)。
没有这三样,评审就只是一次“大家聊过了”。
生产落地:指名 → 可抄 → 可验 → 可退
指名:评审材料用 Markdown/Confluence,模板固定(问题/约束/备选/NFR/数据/失败/回滚/成本/安全/迁移);跨团队评审用 RFC 流程(GitHub PR 或文档评论);结论落 ADR。技术方案写作方法见 80.4 技术方案写作(待补),决策框架见 4.6 决策框架与决策记录。
可抄(评审会开场模板):
## 今天要决定什么
- 决策:订单服务是否采用读写分离(而不是分库)
- 不决定:分片键怎么选(下次单独评审)
## 约束
- P99 ≤ 150ms;峰值 8k QPS;可用性 99.95%
- 团队 4 人;迁移窗口 6 周
## 必须回答的问题
1. 主从延迟对下单有什么影响?怎么兜底?
2. 回滚路径是什么?多久能回?
3. 三年 TCO 与当前方案差多少?
可验:本节检查器真跑过,输出如上;把它放在评审邀请前,材料不齐就不排会。人工验证:随机抽一次已完成的评审,检查是否有 ADR、是否有带 owner 的行动项、结论是否属于三种之一。
可退:评审结论被推翻时,重新评审并更新 ADR(不要删旧记录);有条件通过的条件没满足就上线,按事故处理。评审流程本身太重时,先减评审人、缩会议,而不是取消评审。
反模式
| 反模式 | 后果 |
|---|---|
| 评审会变成作者答辩 | 评审人不敢提反对,方案缺陷被带到线上 |
| 没有备选方案 | 评审只能二选一:同意或得罪人 |
| 评审在实现之后 | 沉没成本让“驳回”不可行 |
| 20 人旁听、3 人发言 | 责任分散,没人对结论负责 |
| 结论是“原则上同意” | 没有授权也没有约束,后续扯皮 |
| 没有 ADR 与行动项 | 三个月后没人记得决定了什么 |
| 只挑格式/命名 | 真正的风险(数据、失败模式)没人看 |
| 评审人=作者 | 自己审自己,失去纠错意义 |
自检
Q1 架构评审和代码评审的边界在哪?
▸ 答案:架构评审看难以逆转、跨团队、影响非功能指标的决策(数据模型、分片键、运行时、接口契约、成本);代码评审看实现质量(正确性、可读性、命名、测试)。架构评审不替代代码评审,代码评审也不该决定架构。
Q2 为什么“评审人越多越好”是错的?
▸ 答案:评审的价值来自“真正相关的人认真读并提问”,不是人数。人越多,责任越分散、发言越少、会议越长,最后变成旁听。3–5 个覆盖数据、SRE、安全、成本等关键视角的人足够;其他人看 ADR 即可。
Q3 评审为什么必须在“不可逆投入之前”?
▸ 答案:评审的目的是纠错,纠错成本随投入增加而上升。实现完之后,推翻方案意味着重写,团队会倾向于“差不多就上”,评审退化成背书。越早评审,备选方案越多、改动越便宜。
Q4 “有条件通过”必须写清什么?
▸ 答案:每条条件、owner、截止时间、验收方式。缺任何一项,“有条件”就会变成“没条件”:没人负责、没人验收,条件永远不满足。主持人要在结论里逐条列出,并在截止时间回看。
Q5 评审结束后必须产出哪三样东西?
▸ 答案:① ADR(记录背景、备选、决策、后果);② 带 owner/截止/验收的行动项;③ 若决定“先欠着”,登记为技术债。没有这三样,评审就只是聊天,无法追溯也无法执行。
关联
- 前置:4.6 决策框架与决策记录、58.1 ADR 实践
- 并列:58.5 技术选型流程
- 下游:58.2 技术债管理、58.4 演进路线图
- 待补:80.4 技术方案写作