Table of Contents

 

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,然后给抽取的方法起一个新的名字。

 

 

 

 

Logo

低代码爱好者的网上家园

更多推荐