2. 核心要点拆解:到底在查什么
我自己在做这套流程时,把检查清单固定成7大类,每一类背后都有对应的失败案例在支撑,不是拍脑袋定的:
2.1 逻辑正确性
这是review的第一优先项。逻辑错了,后面全是白搭。具体看几个点:
- 边界条件有没有想清楚?比如数组越界、字符串为空、集合只有1个元素、数值为0或负数、时间戳刚好是整点等,这些边界最容易出bug。
- 分支判断是否覆盖了所有可能性?if-else写全了吗?有没有隐含的else分支(即“默认情况”)会走错逻辑?
- 循环终止条件是否正确?会不会死循环?会不会少循环一次?
- 异步场景下的竞态条件:有没有可能在回调还没回来时,另一个请求已经改了状态?
我举个真实例子:某次review发现一个支付回调处理函数,订单状态判断用的是if (status == 1)而不是if (status == 1 || status == 2),结果用户在“已支付”状态下再次收到回调时,直接走入了异常分支,订单被标记成“支付失败”。这种bug靠测试都很难发现,因为测试只覆盖了正常流程。
2.2 代码规范与风格一致性
规范问题看似小,但累积起来会让代码库迅速腐烂。团队里只要有一个人不守规范,其他人为了“保持风格统一”往往会直接骂人而不是帮他改。所以在review时需要关注:
- 命名是否清晰达意:变量名、函数名、类名是否体现了它们各自的职责?
data、temp、flag这类命名其实都应该被标红。 - 格式是否与团队规范一致:缩进、空格、换行、引号风格、是否加分号等。这类问题最好交给linter自动检查,人工review不应该浪费精力在纯格式问题上。
- 函数长度与圈复杂度:一个函数超过50行、if嵌套超过3层时,基本就该考虑拆分了。圈复杂度(Cyclomatic Complexity)过高意味着测试难度和出错概率都在上升。
- 是否重复造轮子:同样逻辑写了三遍?能不能提取成公共函数?有没有现成的工具库可用而没用?
2.3 安全性审查
安全在review中往往是最容易被忽略的,因为很多安全漏洞在功能测试阶段看不出来,等到上线才会被人利用。我总结了几类高频问题:
- 输入校验缺失:用户输入直接被拼接到SQL或命令中,导致SQL注入、命令注入。最典型的就是把用户传来的ID直接拼到SQL里,而不用参数化查询。
- XSS漏洞:用户输入没有转义就直接渲染到前端页面,或者
innerHTML直接拼接不可信数据。 - 鉴权/越权问题:接口有没有校验当前登录用户的权限?普通用户能不能访问管理员的接口?ID从请求参数里取,导致水平越权(同一级别用户互访数据)。
- 敏感信息泄露:日志里打了密码或密钥?接口返回了多余的敏感字段?前端代码里硬编码了API密钥?这些一旦上生产就是事故。
- 文件上传安全:上传的文件类型校验是否可信?是不是只检查了Content-Type?有没有限制文件大小?上传目录是否可执行?
我在实践中发现,很多团队review时根本不看安全问题,默认“有别人会看”。但代码评审恰恰是安全防线最前、成本最低的一环,比上线后靠WAF和漏洞扫描来兜底有效得多。
2.4 可读性与可维护性
代码写出来不是给机器看的,机器反正都认识0和1。真正要给你自己、你的同事、三个月后的你看的。所以我review时非常关注可读性:
- 有没有注释?注释是否跟实际行为一致?最讨厌的是“改了代码没改注释”,注释描述的是旧逻辑,直接误导。我一般会要求“注释宁缺毋滥”,但必须准确,不能跟代码打架。
- 命名是否自解释:一个叫作
isValid()的函数一看就知道返回值是布尔。processData()这种谁都看不出来具体干什么的名字,其实等于没名字。 - 代码块的职责是否单一:一个模块/函数能不能在一句话里说清楚它做什么?如果说要两句话才能说清楚,那这个函数大概率“多管闲事”了。
2.5 错误处理与边界值处理
很多崩溃性问题归根到底都是“该处理的情况没处理”。我每次review都会专门盯几个方向:
- 外部依赖不可靠:第三方接口超时、数据库连接偶尔失败、Redis挂掉,代码到底怎么退避重试?会不会无限制重试导致雪崩?
- 空值处理:从接口返回的数据,直接
.length、.toString()前是不是先判断了空指针?防御式编程该不该用?要用在哪一层? - 异常与错误码:异常信息是否可读?是否能把上下文(比如:哪个用户、哪个订单、哪个时间)带出来?错误码能不能直接定位到具体错误位置?
- 日志记录:出错时报不报日志?日志级别选对没有?有没有可能在日志里打印隐私数据?
2.6 性能与资源管理
性能问题在review阶段就能发现一大半,不用等到压测。我关注的主要是:
- 循环里的慢操作:for循环里有没有发HTTP请求、查数据库?这种通常应该提到循环外面或合并批量查询。
- N+1查询问题:ORM框架下先查了10个用户,再循环去查每个用户的订单,产生10次查询。正确做法是
IN一次查出所有人的订单,再在内存里分组。 - 重复计算:循环里反复计算同一个不变的值?应该提到外面缓存。
- 资源未释放:文件流、数据库连接、HTTP连接、定时器,用完之后关没关?在异常路径上关没关?用不用try-with-resources?
- 大对象持有:从大文件或大表里一次性加载所有数据到内存?考虑分批处理或流式处理。
2.7 变更影响范围
这一点我特别想强调,但是很多review清单里都没有单独列出来。每次提交PR,你都要问三个问题:
- 这个改动是否影响已有功能?有没有关联的老测试被改坏了?
- 这个改动是否涉及公共接口(API、数据库表结构、消息格式等)的变更?下游系统知道吗?
- 这个改动是否针对当前任务?如果顺手改了好几处无关代码,review时很难评估真实影响,建议拆分为多个PR。
我自己遇到过一次事故:一个同事在修bug时,顺手改了一个“看起来没用”的工具函数内部逻辑,结果这个工具函数被十几个模块复用,直接导致线上另外两个功能挂了。这就是典型的“影响范围不明确”。所以review时如果看到与本次需求无关的改动,必须要求发起人说明理由,说不清楚就打回。