Code Review 到底在关注些什么?
Code Review 是软件开发过程中非常重要的一个环节,其主要的目的是在项目早期找到和修正错误、提升软件质量。
本文主要关注的是在做 Code Review 的时候,我们主要在关心代码的哪些方面来进行说明。
每个人的关注点不尽相同,于我而言,我的关注点一般在下面的几个部分上:
基础篇 - 包括编码规范、风格、日志规范、内存泄漏等
进阶篇 - 包括是否有较好的抽象、数据库变更检查等
高阶篇 - 包括应急方案、失败性考虑等
接下来,我们逐一来看一下。
基础篇
基本编码规范
类、方法命名是否规范、清晰
方法参数是否过多(比如 6 个以上)、方法体是否过大
重复代码
是否有“魔数”,比如 == 1 处理什么逻辑?(建议有良好的注释说明,定义常量或枚举代替)
... ...
日志规范
日志打印是否有意义,比如像如下几种,这种日志对问题排查等没有啥意义
日志打印可以合并的合起来打印
日志是否有用 MDC,如有需要检查是否合理的调用了 remove / clear 方法,防止线程间日志打印干扰等问题。
... ...
经验性检查
SimpleDateFormat 是否被定义成一个全局变量,如下代码,多线程将出现问题。
SimpleDateFormat 更多的信息请参考《SimpleDateFormat线程不安全示例及其解决方法》
金额处理是否使用 BigDecimal 类型,不能使用 float 和 doube。
另外,BigDecimal 对象创建,如果没有使用好,也可能出现问题。比如下面的示例:
文件流使用后是否正常关闭。我们需要 finally 关闭流,以防止内存泄漏。
... ...
超时问题
新增的服务,如 Dubbo 服务,需要有超时设置
分布式锁需要有超时处理
调用下游接口或者调用各种中间件需要有超时的机制
... ...
代码职责和调用关系
代码职责是否清晰,比如 UserService 写了很多权限资源相关的逻辑处理方法就不好。
层次调用是否正确,一般 Controller -> Service -> DAO 。如果 Controller -> DAO 就不好
Dubbo 服务不要调用调用自己的服务。这个怎么理解呢?比如有一个 UserRpcService,其有 update 和 query 的接口,假设,update 里面需要做用户查询的操作做判断,刚好 query 能够满足。
不建议在 rpc 接口,再调用自身的 rpc 接口,如本示例的 update 和 query。
... ...
线程池创建线程
对一些多线程逻辑,使用线程池创建线程,而不是通过 new Thread 等方式创建。
... ...
进阶篇
数据库方面检查
对表增加字段 Alter table xxx add column xxxx ... ,需要检查下当前表中的记录数,如果数据量很大这个是不能做的。需要同 DBA 进行沟通。或者自己通过建一个新表(比原表多一个字段),用操作新表替换旧表来完成。这里需要完成原表数据迁移等其他内容。
索引添加是否合适
是否存在危险 SQL,如 update / delete 语句中的变量是否在业务能够保证有必要的值,不能出现很多值没有,导致 if test 都不满足,导致更新的范围扩大。
查看是否有循环单个插入记录的情况,改成批量插入。
... ...
安全渗透方面检查
文件上传是不是只判断了文件后缀? 只判断后缀,攻击方可以将一个 jsp 等文件伪装成 jpg 等格式的文件,从而成功上传到服务,导致服务器信息泄漏。
短信验证码对一个手机号,是否调用接口就能给用户发一个新的短信验证码,从而造成短信轰炸?我们需要在短信产生的有效期内保证不重新产生短信验证码。
接口是否存在越权查看等风险?比如 A 可以通过 id 查看属于 B 的设备信息?
... ...
接口保护检查
列表查询是否有 pageSize 的限制(如最多一次查询 100 条)。如果不限制,那么假设 pageSize 可以为 5000 条,那真的是简直了,对吧?
如果接口调用需要有次数限制,我们还要考虑是否对方法等有限流的措施?
... ...
模式应用
如果使用了 D.C.L(Double-Check Lock),那么看是否有 volatile 修饰
抽象是否充分?比如,有不同类型的服务接口调用,主要有如下几个步骤:
如果每个服务都自己写一遍,不是很合适,也不不容易维护和扩展。在这种情况下,不同的服务调用主要是步骤 #1 和步骤 #3 有个性化的处理,可以抽象出来。
比如写一个简单的模版,步骤 #1 和步骤 #3 使用 abstract 方法,由子类具体实现。
抽象类
服务请求参数
决策流服务处理类
决策工具服务处理类
可以考虑一些设计原则,比如单一职责/接口隔离等。
可以参考《设计模式几大原则》
... ...
高阶篇
在这个篇章部分,我们要对一些“失败”的场景,方案 &应急有一定的考虑。
重试和幂等
针对系统间的数据同步,如果失败?我们是否有重试机制?
针对计费等场景,失败后重试调用,我们的接口是否支持幂等?
文件导入任务,如中断,我们是否有重启任务的机制,继续完成?
... ...
方案和应急
缓存对象增加属性(比如 User 加一个 type),发布上线的时候,缓存的 Key 是否有做版本调整。如果升级后 Key 不变,可能导致 Redis 的 value 是由原服务更新,导致新改的内容上线后,可能还是取的原来的值(不包含 type)。
比如在某些场景中,Redis 缓存添加是不是有开关(一般由配置中心推送设置),以防止在缓存不是很正确的场景下,用数据库来保底
比如涉及数据迁移或者 Redis 集群升级(由 5.0 改成 6.0), 切流的计划是否合理?
有时候为了减少 RPC 调用或者少走网络,会结合 Redis(分布式) + Guava(本地缓存)结合使用,本地缓存更新的策略是什么?(集群下需要通过消息广播来达到快速更新各机器本地缓存的目的)
缓存存放的值是否为大对象,缓存个数多大?失败策略是什么?缓存雪崩/并发等场景是否有考虑等等
... ...
更多缓存的一些知识,读者可以从之前的文章《啊哈!缓存》了解更多内容。
小结
本文主要从关注代码本身,对 Code Review 做了简易的说明,想到啥就写了点啥。
Code Review 对代码的关注点,远不止这些,本文也算是一个简单的抛砖引玉,有兴趣的读者可以一起留言探讨。
评论