上周五下午,团队在会议室里等架构师老张来评审新模块的接口设计。他没带PPT,也没翻文档,坐下就问:‘这个接口上线后,第一个月预计多少QPS?峰值会打穿哪层缓存?’——问题一出,开发小李愣了两秒,赶紧打开压测报告翻数据。
技术评审不是盖章仪式
很多团队把评审当成流程卡点:提个PR、填个表、拉个会、签个字。结果代码上线后才发现,数据库没建索引,配置中心没灰度开关,消息队列重试策略写死了3次——这些都不是实现细节,是架构层面的断点。
老张的习惯是:提前半小时看材料,但只扫三样——接口契约、部署拓扑图、异常流图。其余细节等会上当场问。他常说:‘能写进文档的,大概率已经想清楚了;真卡住的,往往藏在没写出来的假设里。’
盯住四个真实场景
1. 故障怎么快速止血?
不是问‘会不会挂’,而是问‘挂了之后,5分钟内能否切到降级逻辑?降级返回的数据,下游敢不敢直接展示给用户?’比如订单服务依赖风控,那风控超时3秒时,订单是直接失败,还是走默认策略生成单号?这个逻辑得在代码里一眼可查,不能靠注释或口头约定。
2. 数据什么时候变脏?
一个电商库存扣减,本地事务+Redis计数+ES同步,三个地方更新节奏不同。评审时老张会让开发画出时间轴:A时刻扣DB,B时刻删缓存,C时刻发MQ。然后问:‘如果B和C之间机器宕机,用户刷页面看到的库存比实际多还是少?’
3. 扩容能不能只动配置?
有个服务按城市分片,扩容新城市时,要改代码加if-else还是改配置中心一条JSON?老张会打开配置平台,现场新增一个测试城市ID,看服务是否自动加载分片规则并接流量。能跑通,才算过关。
4. 日志能不能反向定位问题?
不是检查有没有log.info,而是看关键路径上是否有贯穿全链路的traceId,以及每个日志是否自带上下文。比如支付回调处理中,一行日志里除了订单号,还得有第三方流水号、回调原始body的MD5(脱敏后)。不然半夜告警,光靠订单号根本捞不到原始请求。
代码里藏不住的破绽
有次评审一个文件上传服务,核心方法签名是:
public void handleUpload(MultipartFile file, String userId)老张停顿两秒,说:‘userId从哪儿来的?是JWT解析的,还是前端传的?如果是后者,这个参数应该叫unsafeUserId,而且方法开头必须校验它和JWT里的是否一致。’
一句话点出越权风险。后来发现,确实有运营同学用Postman伪造userId批量上传测试数据。
还有次看Kafka消费者,看到这样一段:
try {
process(message);
} catch (Exception e) {
log.error("消费失败", e);
// 无其他操作
}老张没说话,默默在白板上画了个图:消息重复、消息丢失、下游不可用——这三个状态,这段代码一个都没覆盖。最后改成带死信队列+人工干预开关的版本。
技术评审不是挑刺大会,是帮团队把模糊的‘应该没问题’,变成清晰的‘这里必须这样’。老张的电脑桌面一直挂着一张便签,上面是他自己写的:‘今天少问一句为什么,明天多修三天Bug。’