代码评审实战指南:打造高质量、可维护的项目
- 作者
What?什么是 CR
Code Review(代码评审)指的是让第三者来阅读作者修改的代码,以发现代码中存在的问题。
Why?为什么要做 CR
- 保证代码的质量
- 团队代码规范性的保证
- 技术的交流和分享
- 便于了解需求整体的进度
How ? 如何进行 CR
Design:程序设计、架构设计是否设计合理
- Controller 里面直接传入的是 DO 对象
反例: Controller 里面直接传入的是 DO 对象
public ResultDTO<String> savePersonalFinance(@ApiParam @RequestBody AccountEcomFinanceAuditDO entity, @RequestHeader(value = "Accept-Language") String language) {
String bizId = ecomFinanceAuditService.savePersonalFinance(entity, language);
return ResultDTO.ok(bizId);
}
- 全都是 Service,没有良好的分层和分包
- 过大的类和方法
Functionality:代码功能是否符合作者预期,代码行为是否用户友好
- 加锁示例
反例:
Lock lock = new ReentrantLock();
try {
lock.lock();
System.out.println("hello");
} finally {
lock.unlock();
}
正例:
Lock lock = new ReentrantLock();
lock.lock();
try {
System.out.println("hello");
} finally {
lock.unlock();
}
- 事务里面不允许调用外部接口
在事务中调用外部接口可能会产生以下问题:
性能问题:外部接口的响应时间可能会很长,这会导致事务的执行时间增加,从而影响系统的性能。
事务回滚问题:如果外部接口的调用失败,可能需要回滚事务。但是,如果外部接口的操作不能回滚,那么就可能导致数据的不一致。
事务超时问题:如果外部接口的响应时间过长,可能会导致事务超时。
错误回滚问题:如果外部接口超时且没有正确的判断异常,在使用 @Transactional(rollbackFor = Exception.class) 注解的情况下会错误的回滚数据。
Complexity:实现是否能简化,代码可读性是否良好,接口是否易用
如何减少 if else?
- Return Early
优化前:
public String findUser(String userId) {
if (userId == null) {
return null;
} else {
User user = database.findUserById(userId);
if (user == null) {
return null;
} else {
return user.getName();
}
}
}
优化后:
public String findUser(String userId) {
if (userId == null) {
return null;
}
User user = database.findUserById(userId);
if (user == null) {
return null;
}
return user.getName();
}
- 表驱动
优化前:
if (param.equals(value1)) {
doAction1(someParams);
} else if (param.equals(value2)) {
doAction2(someParams);
} else if (param.equals(value3)) {
doAction3(someParams);
}
// ...
优化后:
Map<?, Function<?> action> actionMappings = new HashMap<>();
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});
actionMappings.get(param).apply(someParams);
- 职责链模式
优化前:
public void handle(request) {
if (handlerA.canHandle(request)) {
handlerA.handleRequest(request);
} else if (handlerB.canHandle(request)) {
handlerB.handleRequest(request);
} else if (handlerC.canHandle(request)) {
handlerC.handleRequest(request);
}
}
优化后:
public void handle(request) {
handlerA.handleRequest(request);
}
public abstract class Handler {
protected Handler next;
public abstract void handleRequest(Request request);
public void setNext(Handler next) {
this.next = next;
}
}
public class HandlerA extends Handler {
public void handleRequest(Request request) {
if (canHandle(request)) doHandle(request);
else if (next != null) next.handleRequest(request);
}
}
- Tests:是否提供了正确、设计良好的自动化测试、单元测试
反例:
@Test
public void testAddWhiteIpList_KjEnterpriseExtProviderReturnsNoItems() {
// Setup
final SsoUser ssoUserDO = new SsoUser();
ssoUserDO.setSysChannel("sysChannel");
ssoUserDO.setAuthCode("authCode");
ssoUserDO.setIp("ip");
ssoUserDO.setLoginName("loginName");
ssoUserDO.setId(0L);
when(mockEnterpriseExtProvider.getUserWhiteConfig("loginName")).thenReturn(Collections.emptyList());
// Run the test
whiteIpListServiceUnderTest.addWhiteIpList(ssoUserDO);
// Verify the results
}
正例:
public void testBasicCatcherDistribution() {
StringCatcher catcher = new StringCatcher();
bus.register(catcher);
bus.post(EVENT);
List<String> events = catcher.getEvents();
assertEquals("Only one event should be delivered.", 1, events.size());
assertEquals("Correct string should be delivered.", EVENT, events.get(0));
}
Naming:变量名、类名、方法名等字面量的选择是否清晰、精炼
- 方法应该清晰,精炼
反例1:
// flag 不需要
boolean flag = false;
for (Long assetId : assetIdList) {
// 在循环里面套数据库查询会有性能问题,最好是通过 id 列表批量查询
MpAssetInfoDO assetInfoDO = assetInfoService.getById(assetId);
if (distributePrefilter(assetInfoDO)) {
flag = true;
break;
}
}
if (flag) {
throw new CommonException("Asset has lapsed, please refresh and try again");
}
- 对于常用的工具和方法要统一
// 优化前
private List<FundsPartyConfigDetailDTO> underConfigIntersection(List<FundsPartyConfigDetailDTO> a, List<FundsPartyConfigDetailDTO> b) {
if (ListUtils.isEmpty(a) || ListUtils.isEmpty(b)) {
return new ArrayList<>();
}
return a.stream().filter(item ->
{
for (FundsPartyConfigDetailDTO configDetailDTO : b) {
if (configDetailDTO.getId().equals(item.getId())) {
return true;
}
}
return false;
}).collect(Collectors.toList());
}
// 优化后
private List<FundsPartyConfigDetailDTO> underConfigIntersection(List<FundsPartyConfigDetailDTO> a, List<FundsPartyConfigDetailDTO> b) {
if (CollectionUtils.isEmpty(a) || CollectionUtils.isEmpty(b)) {
return Collections.emptyList();
}
Set<Long> idSet = b.stream()
.map(FundsPartyConfigDetailDTO::getId)
.collect(Collectors.toSet());
return a.stream()
.filter(item -> idSet.contains(item.getId()))
.collect(Collectors.toList());
}
Comments:是否编写了清晰的、有用的注释
- 注释是给别人看的,目标是让别人知道自己这么写的原因
Style:代码风格是否符合规范
- 新增代码要做格式化
- 不要直接全量格式化之前的代码
Documentation:修改代码的同时,是否同步更新了相关文档
- 代码要和设计文档保持一致
实施注意
- 小批量:每次 Review 的代码量要少,单次 Review 的代码太多,占用评审人的评审时间太长,很容易沦为走过场,建议为 200 行左右,最多不超过 1000 行。
- 多批次:Review 要频繁发生,小步快跑,建议开发同学一周有 3-4 次 CR。
- 快速响应:提交完的 CR,评审人尽量在当前完成当次评审,提交 MR 的时候也尽量选取对相关代码比较熟悉的评审人。
- Release 分支只允许合并,不允许直接推送。
- Release 分支合入到 master 分支由模块负责人操作。
参考
分享内容