代码审查概述
什么是代码审查?
软件同行评审
为什么要代码审查
- 提高代码质量
- 上下文共享
- 新人快速融入项目
- 协助开发人员快速成长
代码审查频率
- 集中式
- 异步式
代码审查流程
- 范根检查法
- 规划
- 概述
- 准备
- 审查会议
- 调整修复
- 跟进
- 结对
- 编程
- 同步代码审查
- 异步代码审查
代码审查工具
- git
- gerrit
- upsource
- gitlab
- github
Checklist
编码风格
命名风格
- 不以下划线或者美元符号开头或者结束
- 类名UpperCamelCase
- 方法、参数名、变量等统一采用 lowerCamelCase 风格
- 常量命名使用全大写,单词间用下划线分割
- 包名统一使用小写
- 禁止使用拼音命名
- 禁止不规范的缩写
- 多个参数逗号后需要加空格
- 大括号换行
常量定义
-
不允许任何硬编码
反例:
String username = "user_" + userId;
-
在 long 或者 Long 赋值时,使用大写的 L
-
不要一个常量类维护所有常量,按功能归类、分开维护
-
常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量
-
常量可穷举,考虑使用枚举
代码格式
- if/for/while/switch/do 等保留字与括号之间都必须加空格
- 任何二目、三目运算符的左右两边都需要加一个空格
- 注释的双斜线与注释内容之间有且仅有一个空格
- 单行字符数限制不超过 120 个,超出需要换行
- 方法参数在定义和传入时,多个参数逗号后边必须加空格。
- 单个方法的总行数不超过 80 行
正例:代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共性逻辑抽取成为 共性方法,便于复用和维护。
- 没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐。
反例:
public static final float LoginLayoutWidth = 380.4f;
public static final float LoginLayoutHeight = 238.2f;
OOP规约
- 静态方法/变量使用类名访问
- 复写方法增加 @Override
- 禁止过时方法/类的使用
- 包装类型比较使用 equals
- 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
- Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals
- 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读
- 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法
- 慎用 Object 的 clone 方法来拷贝对象
- 类成员与方法访问控制从严
分支控制
- switch 中,每个 case 必须使用 continue/break/return 终止或者注释说明到哪个 case 终止,且必须包含一个default 语句
- switch 传入字符需要判空
- 表达异常的分支时,少用 if-else 方式,这种方式可以改写成:
if (condition) {
...
return obj;
}
// 接着写 else 的业务逻辑代码;
- 超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。
卫语句示例:
private void login() {
mUserInfo = getUserInfo();
if (mUserInfo == null) {
showLoginView();
return;
}
boolean logout = mUserInfo.isLogout();
boolean loginExpired = mUserInfo.isLoginExpired();
if (logout) {
ViewController.showSwitchAccount();
return;
}
if (!loginExpired) {
loginByToken(mUserInfo);
return;
}
// ...
}
- 不要在条件判断中执行其它复杂的语句,将 复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性
正例:
boolean isValidUsername = username != null && username.length()>6 && (...) && (...)
if(isValidUsername) {
...
}
反例 :
if(username != null && username.length()>6 && (...) && (...)) {
...
}
- 避免采用取反逻辑运算符
正例:使用 if (x < 628) 来表达 x 小于 628。
反例:使用 if (!(x >= 628)) 来表达 x 小于 628。
注释
- 见名知意,好的命名、代码结构是自解释的,注释力求精简准确、表达到位(如:listUsers、getUserId、loginByToken)
- 方法内部单行注释,使用 // 在需要被注释的语句上方单起一行
- 无用代码删除,而不是注释
- 对外公共接口必须添加注释
- 所有的类都必须添加创建者和创建日期
- 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑
等的修改
- 对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含
义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同
天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看
的,使其能够快速接替自己的工作
功能性
- 代码是否符合用户意图
- 验证输入、输出
- 便捷是否处理妥当
- 是否有高并发问题
测试覆盖
- unit
- service
- UI
复杂度
- 可读性
- 可维护性
- 缺陷概率
- 模块内聚性
- 复杂度优化
- 方法抽取
- 反向表达
- 单一职责
- 多态
设计
- 目的
- 提升系统健壮性
- 提升可读性
- 提升可扩展性
- 是否足够解耦?
- 是否可以使用一些设计模式?
- 是否可以应对变化?
- 是否过度设计?
安全性
- 程序运行异常
- 数据易泄漏
- 资源消耗异常
- 数据库凭据
- 敏感数据是否加密落库
- 输出是否安全
- 权限管控
- 返回值谨慎用null