设计评审清单
设计评审清单用于在动手写代码之前,对技术方案做系统性审视。目标是在开发前发现问题,而不是在测试或上线后补救。
要点
- 设计评审在写代码之前进行,针对影响范围超过单模块的改动
- 评审不是挑错,是提前发现问题、统一认知
- 评审前必须准备材料:问题陈述、目标、方案、取舍、影响范围、风险
- 重点检查:目标可衡量、方案可行、兼容性、异常处理、可观测性、回滚方案
1. 适用时机
需要设计评审的场景:
- 新功能开发(影响范围超过单个模块)
- 已有功能的重大改动(改 API、改数据结构、改业务流程)
- 跨团队协作的需求(涉及多个团队或外部系统)
- 引入新技术或第三方依赖
不需要设计评审的场景:
- 修 bug(但需要查清根因)
- 纯 UI 调整(不影响业务逻辑)
- 配置变更(有回滚方案即可)
2. 评审前准备
评审不是临时拉会讨论。发起人需要提前准备以下材料,否则评审会沦为泛泛而谈。
必须准备
- 问题陈述:要解决什么问题?为什么要现在解决?不解决会怎样?
- 目标与非目标:这次改动要达成什么,明确不做什么
- 方案描述:打算怎么做?至少给一个具体方案
- 取舍说明:为什么选这个方案?考虑过哪些替代方案?各自优缺点?
- 影响范围:涉及哪些模块、接口、数据表、依赖方?
- 风险评估:可能出什么问题?最坏情况是什么?
- 验证方式:怎么判断方案有效?上线后看什么指标?
可选但推荐
- 时序图/流程图:复杂交互用图比文字清楚
- 数据量估算:预估数据规模、QPS、存储需求
- 时间估算:开发、联调、测试、灰度、全量各需要多久
3. 核心检查项
3.1 问题与目标
- 问题陈述清晰,不是「优化一下」这种模糊描述
- 目标可衡量(「响应时间从 2s 降到 500ms」而不是「提升性能」)
- 非目标明确,避免评审过程中范围蔓延
- 成功标准定义清楚,上线后能判断是否达成
3.2 方案可行性
- 技术选型合理,不是「因为流行所以用」
- 现有团队能维护这个方案,不是「只有作者能看懂」
- 时间估算合理,不是「这周一定能上」
- 不依赖尚未就绪的外部条件(别人还没开发好的接口、还没采购的服务)
Hono 示例:技术选型的判断
// 不好的选型理由
// 「因为 Express 流行所以用 Express」
// 好的选型理由
// 1. 团队熟悉 Hono,学习成本低
// 2. Hono 支持 Cloudflare Workers,符合边缘部署需求
// 3. Hono 性能优于 Express,P99 延迟更低
// 4. Hono 有 OpenAPI 集成,文档自动生成
import { Hono } from 'hono'
const app = new Hono()
// 选型决策记录
// 选择:Hono + Cloudflare Workers
// 放弃:Express + Node.js
// 理由:边缘部署延迟更低(< 50ms vs 200ms)3.3 兼容性与迁移
- 对现有功能无破坏(或明确列出破坏性变更,给迁移方案)
- 数据迁移方案完整(旧数据怎么办、能否回滚、需要多长时间)
- API 兼容性考虑(老客户端能不能继续用、是否需要版本化)
- 配置变更平滑过渡(新旧配置能否共存)
Hono 示例:API 版本化
import { Hono } from 'hono'
const app = new Hono()
// v1 API - 保持兼容
const v1 = new Hono()
v1.get('/users', (c) => {
return c.json({ users: [] })
})
// v2 API - 新增字段,不删除旧字段
const v2 = new Hono()
v2.get('/users', (c) => {
return c.json({
users: [],
pagination: { page: 1, total: 100 } // 新增字段
})
})
// 路由挂载
app.route('/api/v1', v1)
app.route('/api/v2', v2)
// 废弃通知
app.get('/api/v1/users', (c, next) => {
c.header('Deprecation', 'true')
c.header('Sunset', 'Sat, 01 Jan 2027 00:00:00 GMT')
return next()
})3.4 边界与异常
- 主路径走通,正常情况没问题
- 异常路径有处理(网络失败、第三方挂了、数据异常)
- 并发场景考虑(多用户同时操作、重复提交)
- 大数据量场景考虑(分页、批处理、索引)
- 安全边界清楚(谁能访问、谁能修改、敏感数据如何处理)
Hono 示例:异常处理
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// 统一的错误处理
app.onError((err, c) => {
console.error('Unhandled error:', err)
if (err instanceof HTTPException) {
return c.json({
code: err.message,
message: '请求处理失败',
details: err.name
}, err.status)
}
return c.json({
code: 'INTERNAL_ERROR',
message: '服务内部错误',
details: process.env.NODE_ENV === 'development' ? err.message : undefined
}, 500)
})
// 业务异常
app.post('/orders', async (c) => {
const body = await c.req.json()
// 库存检查
const stock = await checkStock(body.productId)
if (stock < body.quantity) {
throw new HTTPException(409, { message: 'INSUFFICIENT_STOCK' })
}
// 并发控制:幂等键防止重复提交
const idempotencyKey = c.req.header('Idempotency-Key')
if (idempotencyKey) {
const existing = await c.env.KV.get(`order:${idempotencyKey}`)
if (existing) {
return c.json(JSON.parse(existing), 200)
}
}
// 创建订单
const order = await createOrder(body)
// 保存幂等键
if (idempotencyKey) {
await c.env.KV.put(
`order:${idempotencyKey}`,
JSON.stringify(order),
{ expirationTtl: 86400 }
)
}
return c.json(order, 201)
})3.5 可观测性
- 关键操作有日志,出了问题能排查
- 核心指标有监控,能发现问题(错误率、延迟、QPS)
- 关键流程有追踪(trace id 贯穿)
- 异常有告警,不用等用户反馈
Hono 示例:日志与追踪
import { Hono } from 'hono'
import { logger } from 'hono/logger'
const app = new Hono()
// 请求日志
app.use('*', logger())
// 自定义中间件:添加 trace id
app.use('*', async (c, next) => {
const traceId = c.req.header('X-Trace-ID') || crypto.randomUUID()
c.set('traceId', traceId)
c.header('X-Trace-ID', traceId)
const start = Date.now()
await next()
const duration = Date.now() - start
// 结构化日志
console.log(JSON.stringify({
traceId,
method: c.req.method,
path: c.req.path,
status: c.res.status,
duration,
timestamp: new Date().toISOString()
}))
})
app.get('/orders/:id', async (c) => {
const traceId = c.get('traceId')
const orderId = c.req.param('id')
// 日志带 trace id
console.log(JSON.stringify({
traceId,
event: 'order.fetch',
orderId
}))
const order = await getFromDatabase(orderId)
if (!order) {
console.log(JSON.stringify({
traceId,
event: 'order.not_found',
orderId
}))
return c.json({ code: 'ORDER_NOT_FOUND' }, 404)
}
return c.json(order)
})3.6 测试与验证
- 单元测试覆盖核心逻辑
- 集成测试覆盖关键路径
- 有灰度方案(不是「一次全上」)
- 有回滚方案(发现问题能在 15 分钟内恢复)
- 上线后的验证步骤明确(谁、做什么、看什么指标)
Hono 示例:灰度发布
import { Hono } from 'hono'
const app = new Hono()
// Feature Flag 中间件
app.use('/api/new-feature/*', async (c, next) => {
const userId = c.get('userId')
// 从 KV 读取灰度配置
const config = await c.env.KV.get('feature:new-feature', 'json')
const { enabled, percentage, whitelist } = config || {
enabled: false,
percentage: 0,
whitelist: []
}
// 白名单用户
if (whitelist.includes(userId)) {
c.set('featureEnabled', true)
return next()
}
// 按比例灰度
const hash = userId.charCodeAt(0) % 100
if (enabled && hash < percentage) {
c.set('featureEnabled', true)
return next()
}
c.set('featureEnabled', false)
return next()
})
app.get('/api/new-feature/data', (c) => {
const enabled = c.get('featureEnabled')
if (!enabled) {
// 返回旧版本数据
return c.json({ version: 'v1', data: [] })
}
// 返回新版本数据
return c.json({ version: 'v2', data: [] })
})3.7 文档与沟通
- 设计文档写清楚,新同学能看懂
- 依赖方已沟通(涉及别人的接口、数据、资源)
- 上下游已知晓影响(客服、运营、产品)
- 有变更公告或通知(对用户有影响的改动)
4. 评审会上容易漏的点
评审会上大家常常关注主路径和技术细节,忽略以下几个点。主持人可以逐项过一遍。
- 回滚方案:问一句「如果上线后发现问题,怎么回滚?需要多久?」很多人没想过
- 监控告警:问「怎么知道出了问题?」如果答案是「用户反馈」,说明可观测性没做好
- 数据迁移:问「旧数据怎么办?」经常有人只想到了新逻辑,忘了旧数据
- 依赖方:问「这个改动影响谁?他们知道吗?」跨团队问题常在这里暴露
- 成功标准:问「上线一周后,怎么判断这次改动是成功的?」没人看指标等于白做
5. 通过标准
设计评审不是投票,但有共识才能往下走。以下情况算通过:
- 问题陈述、目标、方案三方认可
- 关键风险已识别,有应对措施
- 影响范围清楚,依赖方已沟通
- 验证方式和成功标准明确
- 回滚方案可行
- 评审意见已记录,未解决的问题有跟进人
6. 不通过的情况
以下情况应该打回重做,不要硬上:
- 问题陈述模糊,没人知道为什么要做
- 方案不可行(技术、时间、资源都不允许)
- 关键风险未识别,或识别了没应对
- 影响范围不清,依赖方没沟通
- 没有回滚方案,出问题只能硬扛
7. 维护建议
这份清单不是一成不变的。每次评审后,回顾一下:
- 这次评审有没有漏掉的点?加进清单
- 哪些检查项其实不重要?可以降级或删除
- 哪些项目需要按团队特点调整?(比如 AI 项目要加「prompt 管理」「模型成本」等检查项)
清单的维护者和最近一次更新时间应该记录在文档末尾,便于追溯。