问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

一步步降低代码复杂度

创作时间:
作者:
@小白创作中心

一步步降低代码复杂度

引用
CSDN
1.
https://blog.csdn.net/j16421881/article/details/105608591

目录
1 Sonar认知复杂度计算规则
2 降低复杂度
2.1 降低复杂度前的准备
2.2 一个for循环只做一件事
2.3 减少for循环中的if else、continue
2.4 抽离try/catch
2.5 for循环内容不宜过长
2.6 验证重构
3. 通过IDEA一秒钟实现重构

1 Sonar认知复杂度计算规则

这里的认知复杂度特指代码分析工具Sonar的认知复杂度。

Sonar要求认知复杂度低于15。首先了解一下认知复杂度的计算规则。

Cognitve Complexity的计算:

  • (1)&&、|| 条件判断符号 +1
  • (2)if、else if、else、switch 分支语句+1
  • (3)for、while、do while 循环语句+1
  • (4)catch 捕获异常语句+1
  • (5)break、continue 中断语句+1
  • (6)如果if、for、while、do while、catch存在嵌套时,里层的语句相对于外层+1

来看两个官方示例

2 降低复杂度

了解了复杂度的规则,我们的主要工作就是围绕着如何优雅的减少条件判断来进行。

这里以某方法 restrictionNumLimit 为例展示降低复杂度的若干步骤。

Sonar扫描显示该方法的复杂度为20

restrictionNumLimit逻辑为判断当前下单的商品数量是否超过限购。

2.1 降低复杂度前的准备

首先为restrictionNumLimit编写单元测试,覆盖各个条件边界。

为此编写了一个代码行数5倍于restrictionNumLimit的单元测试,重构前测试全绿。

没有单元测试的重构如同盲人开车,不翻车看运气。

2.2 一个for循环只做一件事

查看代码,for循环里面有if else。逻辑为根据商品Id累加商品数量。

if else,加上for循环,总共贡献了3个复杂度。

//累加商品id相同的sku数量
for (OrderSkuQuery orderSkuQuery : orderSkuQueryList) {
    if (res.containsKey(orderSkuQuery.getItemsId())) {
        res.put(orderSkuQuery.getItemsId(), orderSkuQuery.getSkuNum() + res.get(orderSkuQuery.getItemsId()));
    } else {
        res.put(orderSkuQuery.getItemsId(), orderSkuQuery.getSkuNum());
    }
}

借助 java.util.Collection.stream 、java.util.Map.compute ,在一个表达式里面实现了商品数量的累加,抹除了for循环与if else的认知复杂度。

Map<Long, Integer> res = Maps.newHashMap();
orderSkuQueryList.stream().forEach(orderSkuQuery -> res.compute(orderSkuQuery.getItemsId(),
    (k, v) -> v == null ? orderSkuQuery.getSkuNum() : v + orderSkuQuery.getSkuNum()));

2.3 减少for循环中的if else、continue

for循环中的continue,if括号内过长的条件判断都增加了复杂度。

由于continue跟业务处理无关,先在外围通过java.util.stream.Stream.filter把continue从for循环抽离出去,把过长的条件判断抽取到一个方法内。

如下,for循环中已经没有continue跟过长的条件判断。

2.4 抽离try/catch

try/catch代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。

——《代码整洁之道》

通过把for循环里面的try方法抽取到单独的方法,继续降低复杂度

// 查询历史订单数量,强制查询主库
List<ItemSum> itemSumList = null;
try (HintManager hintManager = HintManager.getInstance()) {
    hintManager.setMasterRouteOnly();
    itemSumList = SpringContextHolder.getBean(OrderSkuDao.class).sumByItemsIdAndUserId(itemsId, openId, 0, openSource.getCode());
}
int hisCount = CollectionUtils.isEmpty(itemSumList) ? 0 : itemSumList.get(0).getSum();  

// 重构为
private int getHistoryItemSums(Long itemsId) {
    List<ItemSum> itemSumList = null;
    try (HintManager hintManager = HintManager.getInstance()) {
        hintManager.setMasterRouteOnly();
        itemSumList = SpringContextHolder
            .getBean(OrderSkuDao.class)
            .sumByItemsIdAndUserId(itemsId, openId, 0, openSource.getCode());
    }
    return CollectionUtils.isEmpty(itemSumList) ? 0 : itemSumList.get(0).getSum();
}

2.5 for循环内容不宜过长

如果一个for循环内容过长只会越来越长。

如下当满足条件:该商品历史购买数量已经超过限购直接就跳出for循环return了,那么return后面的else可以省略。

同时else里面的内容可以抽取到一个方法里面,避免for循环内容过长。

2.6 验证重构

所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。

——《重构:改善既有代码的设计》

重构后要保证代码逻辑的正确,如下重构后测试继续全绿。

3. 通过IDEA一秒钟实现重构

如果一个工具能够为你安全地提取方法,你就不需要自己编写测试去验证提取是否正确。

——《修改代码的艺术》

有时候我们来不及为一个方法写一个超长的单元测试,借助IDEA的Extract Method功能瞬间完成重构,降低认知复杂度。

如下选中超长的条件判断,右键Refactor,选中Extract Method,然后给抽取的方法起一个新的名字。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号