工作一年,我重新理解了《重构》
The following article is from 阿里开发者 Author 尹梦雨(惜时)
前言
一、重构的定义
二、为什么要重构
2.1 提升开发效率
2.2 降低修改风险
三、重构实践
3.1 减少重复代码
public PhotoHomeInitRes photoHomeInit() {
if (!photoDrm.inUserPhotoWhitelist(SessionUtil.getUserId())) {
LoggerUtil.info(LOGGER, "[PhotoFacade] 用户暂无使用权限,userId=", SessionUtil.getUserId());
throw new BizException(ResultEnum.NO_ACCESS_AUTH);
}
PhotoHomeInitRes res = new PhotoHomeInitRes();
InnerRes innerRes = photoAppService.renderHomePage();
res.setSuccess(true);
res.setTemplateInfoList(innerRes.getTemplateInfoList());
return res;
}
public CheckStorageRes checkStorage() {
if (!photoDrm.inUserPhotoWhitelist(SessionUtil.getUserId())) {
LoggerUtil.info(LOGGER, "[PhotoFacade] 用户暂无使用权限,userId=", SessionUtil.getUserId());
throw new BizException(ResultEnum.NO_ACCESS_AUTH);
}
CheckStorageRes checkStorageRes = new CheckStorageRes();
checkStorageRes.setCanSave(photoAppService.checkPhotoStorage(SessionUtil.getUserId()));
checkStorageRes.setSuccess(true);
return checkStorageRes;
}
重构方法:及时清理无用代码、减少重复代码。
public PhotoHomeInitRes photoHomeInit() {
photoAppService.checkUserPhotoWhitelist(SessionUtil.getUserId());
PhotoHomeInitRes res = new PhotoHomeInitRes();
InnerRes innerRes = photoAppService.renderHomePage();
res.setSuccess(true);
res.setTemplateInfoList(innerRes.getTemplateInfoList());
return res;
}
public CheckStorageRes checkStorage() {
photoAppService.checkUserPhotoWhitelist(SessionUtil.getUserId());
CheckStorageRes checkStorageRes = new CheckStorageRes();
checkStorageRes.setCanSave(photoAppService.checkPhotoStorage(SessionUtil.getUserId()));
checkStorageRes.setSuccess(true);
return checkStorageRes;
}
public boolean checkUserPhotoWhitelist(String userId) {
if (!photoDrm.openMainSwitchOn(userId) && !photoDrm.inUserPhotoWhitelist(userId)) {
LoggerUtil.info(LOGGER, "[PhotoFacade] 用户暂无使用权限, userId=", userId);
throw new BizException(ResultEnum.NO_ACCESS_AUTH);
}
return true;
}
我们在系统中或多或少都看到过未复用已有代码产生的重复代码或者已经无流量的代码,但对形成背景不了解,出于稳定性考虑,不敢贸然清理,时间久了堆积越来越多。因此,我们在日常开发过程中,对项目产生的无用代码、重复代码要及时清理,防止造成后面同学在看代码时的困惑,以及不够熟悉背景的同学改动相关代码时漏改、错改的风险。
3.2 提升可读性
3.2.1 有效的注释
List<String> voucherMarkList = CommonUtil.batchfetchVoucherMark(voucherList);
if (CollectionUtil.isEmpty(voucherMarkList)) {
return StringUtil.EMPTY_STRING;
}
BatchRecReasonRequest request = new BatchRecReasonRequest();
request.setBizItemIds(voucherMarkList);
Map<String, List<RecReasonDetailDTO>> recReasonDetailDTOMap = relationRecReasonFacadeClient.batchGetRecReason(request);
if (CollectionUtil.isEmpty(recReasonDetailDTOMap)) {
return StringUtil.EMPTY_STRING;
}
for (String voucherMark : recReasonDetailDTOMap.keySet()) {
List<RecReasonDetailDTO> reasonDetailDTOS = recReasonDetailDTOMap.get(voucherMark);
for (RecReasonDetailDTO recReasonDetailDTO : reasonDetailDTOS) {
if (needUpdateRecMaxCount(recReasonDetailDTO, RecReasonTypeEnum.FRIEND, recTypeList, friendRecMaxCount)) {
friendRecText = recReasonDetailDTO.getRecommendText();
friendRecMaxCount = recReasonDetailDTO.getCount();
friendRecMaxCountDetailDTOS = reasonDetailDTOS;
continue;
}
if (needUpdateRecMaxCount(recReasonDetailDTO, RecReasonTypeEnum.LBS, recTypeList, lbsRecMaxCount)) {
lbsRecText = recReasonDetailDTO.getRecommendText();
lbsRecMaxCount = recReasonDetailDTO.getCount();
}
}
return bulidRecText(friendRecMaxCountDetailDTOS, friendRecText, lbsRecText);
重构方法:补充相应的业务注释,说明方法的核心思想和业务处理背景。
//1.生成对应的券标识,查推荐信息
List<String> voucherMarkList = CommonUtil.batchfetchVoucherMark(voucherList);
if (CollectionUtil.isEmpty(voucherMarkList)) {
return StringUtil.EMPTY_STRING;
}
BatchRecReasonRequest request = new BatchRecReasonRequest();
request.setBizItemIds(voucherMarkList);
Map<String, List<RecReasonDetailDTO>> recReasonDetailDTOMap = relationRecReasonFacadeClient.batchGetRecReason(request);
if (CollectionUtil.isEmpty(recReasonDetailDTOMap)) {
return StringUtil.EMPTY_STRING;
}
//2.解析对应的推荐文案,取使用量最大的推荐信息,且好友推荐信息优先级更高
for (String voucherMark : recReasonDetailDTOMap.keySet()) {
List<RecReasonDetailDTO> reasonDetailDTOS = recReasonDetailDTOMap.get(voucherMark);
for (RecReasonDetailDTO recReasonDetailDTO : reasonDetailDTOS) {
//2.1 获取好友推荐信息
if (needUpdateRecMaxCount(recReasonDetailDTO, RecReasonTypeEnum.FRIEND, recTypeList, friendRecMaxCount)) {
friendRecText = recReasonDetailDTO.getRecommendText();
friendRecMaxCount = recReasonDetailDTO.getCount();
friendRecMaxCountDetailDTOS = reasonDetailDTOS;
continue;
}
//2.2 获取地理位置推荐信息
if (needUpdateRecMaxCount(recReasonDetailDTO, RecReasonTypeEnum.LBS, recTypeList, lbsRecMaxCount)) {
lbsRecText = recReasonDetailDTO.getRecommendText();
lbsRecMaxCount = recReasonDetailDTO.getCount();
}
}
//3.组装结果并返回,若好友推荐量最大的券推荐信息中包含地理位置信息,则返回组合文案(好友推荐信息与地理位置推荐信息均来自同一张券)
return bulidRecText(friendRecMaxCountDetailDTOS, friendRecText, lbsRecText);
重构这本书中表达了对注释的观点,作者认为代码中不应有过多注释,代码功能应该通过恰当的方法命名体现,但相比于国内大多数工程师,书中作者对英文的理解和运用更加擅长,所以书中有此观点。但每个人的命名风格和对英文的理解不同,仅通过命名不一定能快速了解背后的业务逻辑。个人认为,业务注释而非代码功能注释,清晰直观的业务注释能够在短时间内大致了解代码对应的业务逻辑,可以帮助阅读者快速理解为什么这样做,而不是做什么,因此,简洁的业务注释仍然是有必要的。
3.2.2 简化复杂的条件判断
for (RecReasonDetailDTO recReasonDetailDTO : reasonDetailDTOS) {
//2.1 获取好友推荐信息
if (StringUtil.equals(recReasonDetailDTO.getRecReasonType(), RecReasonTypeEnum.FRIEND.name())
&& recTypeList.contains(RecReasonTypeEnum.FRIEND.name()) && StringUtil.isNotBlank(recReasonDetailDTO.getRecommendText())
&& recReasonDetailDTO.getCount() != 0 && Long.valueOf(recReasonDetailDTO.getCount()) > friendRecMaxCount) {
friendRecText = recReasonDetailDTO.getRecommendText();
friendRecMaxCount = recReasonDetailDTO.getCount();
friendRecMaxCountDetailDTOS = reasonDetailDTOS;
continue;
}
//2.2 获取地理位置推荐信息
if (StringUtil.equals(recReasonDetailDTO.getRecReasonType(), RecReasonTypeEnum.LBS.name())
&& recTypeList.contains(RecReasonTypeEnum.LBS.name()) && StringUtil.isNotBlank(recReasonDetailDTO.getRecommendText())
&& recReasonDetailDTO.getCount() != 0 && Long.valueOf(recReasonDetailDTO.getCount()) > lbsRecMaxCount) {
lbsRecText = recReasonDetailDTO.getRecommendText();
lbsRecMaxCount = recReasonDetailDTO.getCount();
}
}
重构方法:将判断条件单独放在独立方法中并恰当命名,提升可读性
for (RecReasonDetailDTO recReasonDetailDTO : reasonDetailDTOS) {
//2.1 获取好友推荐信息
if (needUpdateRecMaxCount(recReasonDetailDTO, RecReasonTypeEnum.FRIEND, recTypeList, friendRecMaxCount)) {
friendRecText = recReasonDetailDTO.getRecommendText();
friendRecMaxCount = recReasonDetailDTO.getCount();
friendRecMaxCountDetailDTOS = reasonDetailDTOS;
continue;
}
//2.2 获取地理位置推荐信息
if (needUpdateRecMaxCount(recReasonDetailDTO, RecReasonTypeEnum.LBS, recTypeList, lbsRecMaxCount)) {
lbsRecText = recReasonDetailDTO.getRecommendText();
lbsRecMaxCount = recReasonDetailDTO.getCount();
}
}
private boolean needUpdateRecMaxCount(RecReasonDetailDTO recReasonDetailDTO, RecReasonTypeEnum reasonTypeEnum,
List<String> recTypeList, long recMaxCount) {
if (StringUtil.equals(recReasonDetailDTO.getRecReasonType(), reasonTypeEnum.name())
&& recTypeList.contains(reasonTypeEnum.name()) && StringUtil.isNotBlank(recReasonDetailDTO.getRecommendText())
&& recReasonDetailDTO.getCount() != 0 && Long.valueOf(recReasonDetailDTO.getCount()) > recMaxCount) {
return true;
}
return false;
}
3.2.3 重构多层嵌套条件语句
if (Objects.nonNull(cardSaveNotifyDTO) && !noNeedSendOpenCardMsg(cardSaveNotifyDTO)) {
CardDO cardDO = CardDAO.queryCardInfoById(cardSaveNotifyDTO.getCardId(),
cardSaveNotifyDTO.getUserId());
if (Objects.isNull(cardDO)) {
LoggerUtil.warn(LOGGER, "[CardSaveMessage] cardDO is null");
return;
}
openCardServiceManager.sendOpenCardMessage(cardDO);
LoggerUtil.info(LOGGER, "[CardSaveMessage] send open card message, cardSaveNotifyDTO=" + cardSaveNotifyDTO);
}
重构方法:对于多层if嵌套的代码,可以将不满足校验条件的情况快速返回,增强可读性。
if (Objects.isNull(cardSaveNotifyDTO)) {
LoggerUtil.warn(LOGGER, "[CardSaveMessage] cardSaveNotifyDTO is null");
return;
}
LoggerUtil.info(LOGGER, "[CardSaveMessage] receive card save message, cardSaveNotifyDTO=" + cardSaveNotifyDTO);
if (noNeedSendOpenCardMsg(cardSaveNotifyDTO)) {
LoggerUtil.info(LOGGER,
"[CardSaveMessage] not need send open card message, cardSaveNotifyDTO=" + cardSaveNotifyDTO);
return;
}
CardDO cardDO = CardDAO.queryCardInfoById(cardSaveNotifyDTO.getCardId(),
cardSaveNotifyDTO.getUserId());
if (Objects.isNull(cardDO)) {
LoggerUtil.warn(LOGGER, "[CardSaveMessage] cardDO is null");
return;
}
openCardServiceManager.sendOpenCardMessage(cardDO);
LoggerUtil.info(LOGGER, "[CardSaveMessage] send open card message, cardSaveNotifyDTO=" + cardSaveNotifyDTO);
如果是程序本身多种情况的返回值,可以减少出口,提升可读性。对于业务代码的前置校验,更适合通过快速返回代替if嵌套的方式简化条件语句。虽然实际上实现功能相同,但可读性及表达含义不同。用多分支(if else)表明多种情况出现的可能性是同等的,而判断特殊情况后快速返回的写法,表明只有很少部分出现其他情况,所以出现后快速返回。简化判断条件更易让人理解业务场景。
3.2.4 固定规则语义化
if (isMrchCardRemind(appId, appUrl)) {
args.put(MessageConstant.MSG_REMIND_APP_ID, appId);
args.put(MessageConstant.MSG_REMIND_APP_URL, appUrl);
if (StringUtil.isNotBlank(memberCenterUrl)) {
args.put(MessageConstant.MEMBER_CENTER_URL, memberCenterUrl);
scene = scene + "_WITH_MEMBER_CENTER";
}
scene = scene + "_MERCH";
}
重构方法:可以将其语义抽象为字段放入枚举中,降低修改时的风险,增强可读性
/**
* 积分变动
*/
CARD_POINT_UPDATE("CARD_POINT_UPDATE", "CARD_POINT_UPDATE_MERCH", "CARD_POINT_UPDATE_WITH_MEMBER_CENTER", "CARD_POINT_UPDATE_MERCH_WITH_MEMBER_CENTER"),
/**
* 余额变动
*/
CARD_BALANCE_UPDATE("CARD_BALANCE_UPDATE", "CARD_BALANCE_UPDATE_MERCH", "CARD_BALANCE_UPDATE_WITH_MEMBER_CENTER", "CARD_BALANCE_UPDATE_MERCH_WITH_MEMBER_CENTER"),
/**
* 等级变动
*/
CARD_LEVEL_UPDATE("CARD_LEVEL_UPDATE", "CARD_LEVEL_UPDATE_MERCH", "CARD_LEVEL_UPDATE_WITH_MEMBER_CENTER", "CARD_LEVEL_UPDATE_MERCH_WITH_MEMBER_CENTER"),
if (isMrchCardRemind(appId, appUrl)) {
args.put(MessageConstant.MSG_REMIND_APP_ID, appId);
args.put(MessageConstant.MSG_REMIND_APP_URL, appUrl);
if (StringUtil.isNotBlank(memberCenterUrl)) {
args.put(MessageConstant.MEMBER_CENTER_URL, memberCenterUrl);
return remindSceneEnum.getMerchRemindWithMemberScene();
}
return remindSceneEnum.getMerchRemindScene();
}
四、思考总结
4.1 去除重复代码
4.2 恰当直观的命名
4.3 单一职责,避免过长的方法
参考阅读:
本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿