Code Smell 重构你的日常代码-圈复杂度高多层嵌套

2022 年 11 月 11 日 阿里技术

前言

圈复杂度(Cyclomatic complexity)[1] 是一种代码复杂度的衡量标准,在1976年由Thomas J. McCabe, Sr. 提出。条件分支越多,圈复杂度越高,测试越难覆盖,也越难维护。随着业务的不断演进,代码的不断新增与调整,如果只在原逻辑下加入自己的新逻辑,就会长出一个超高嵌套的“气功波”代码。
在我们的祖传代码中,“气功波”式代码不占少数。新增一个条件分支成本是相对低的,它可以让你在不了解原逻辑情况下,完成自己的逻辑。但会持续对系统产生负债,直到有一天,我们真的完全不知道修改的这一行代码,到底影响到了哪些~


Bad Smell

在进行一项业务需求时,接触到了这段祖传代码,通过非常高的嵌套,取其中一项值。由于数据结构非常复杂,为了保证代码的健壮性,原作者写了非常多条件判断,形成了这样一段超高复杂性的“气功波”代码。
  
  
    
/** * 解析工单ACTION数据中的【完结原因】 * @param caseId 工单ID * @return */private String queryResolveAction(Long caseId) {    ActionQueryBizDTO actionQueryBizDTO = new ActionQueryBizDTO();    actionQueryBizDTO.setBizId(caseId);   //工单id    actionQueryBizDTO.setDataSource(1);    ActionQueryDTO actionQueryDTO = new ActionQueryDTO();    actionQueryDTO.setBizDTOs(Lists.newArrayList(actionQueryBizDTO));    Result<PageWithData<ActionDTO>> result = ltppActionQueryService.queryActions(actionQueryDTO);    log.info("query action results:{}", JSON.toJSONString(result));    if (result.isSuccess() && result.getData() != null) {        if (CollectionUtils.isNotEmpty(result.getData().getData())) {            for (ActionDTO actionDTO : result.getData().getData()) {                if (ACTION_COMPLETE_CODE.equals(actionDTO.getActionCode())) {                    JSONObject memoObject = JSON.parseObject(actionDTO.getMemo());                    JSONArray actionKeyMemoArray = memoObject.getJSONArray("actionKeyMemo");                    for (Object actionKey : actionKeyMemoArray) {                        Map<String, Object> actionKeyMap = (Map<String, Object>)actionKey;                        if (MapUtils.isNotEmpty(actionKeyMap) && COMPLETE_REASON.equals(actionKeyMap.get("key"))) {                            return String.valueOf(actionKeyMap.get("value"));                        }                    }                }            }        }    }    log.warn("cannot find action given case id {}, the result is {}", caseId, JSON.toJSONString(result));    return null;}


重构思路

1.卫语句返回,减少嵌套层级

卫语句(guard clauses)[2] 是一种改善嵌套代码的优化方案,将某些要害(guard)条件优先作判断,从而简化程序的流程走向。
  
  
    
public static String getCaseQuestionTitle(CaseTaskRelatedDO caseTask){    Map<String, Object> extAttrs = caseTask.getExtAttrs();    if(extAttrs == null || extAttrs.isEmpty()){        return null;    }    JSONObject xform = JSON.parseObject(String.valueOf(extAttrs.get("xform")));    if(xform == null){        return null;    }    JSONObject body = xform.getJSONObject("body");    if(body == null){        return null;    }    return body.getString("question_title");}

2.函数功能收敛,单一职责原则

单一职责原则(Single responsibility principle)[3] 强调一个类应该只有一个发生变化的原因,只负责一处职责,由Robert C. Martin首次在  Agile Software Development  [4]中提出,并成为面向对象五大设计原则之一。
  
  
    
/** * 查询工单Action信息 * K,V -> ACTION_CODE,ACTION * @param caseId * @return */private Map<Integer, ActionDTO> queryCaseActionMap(Long caseId){    ActionQueryBizDTO actionQueryBizDTO = new ActionQueryBizDTO();    actionQueryBizDTO.setBizId(caseId);    ActionQueryDTO actionQueryDTO = new ActionQueryDTO();    actionQueryDTO.setBizDTOs(Lists.newArrayList(actionQueryBizDTO));    Result<PageWithData<ActionDTO>> result = ltppActionQueryService.queryActions(actionQueryDTO);    log.info("query action results:{}", JSON.toJSONString(result));    if(noActionResult(result)){        return null;    }    List<ActionDTO> actionList = result.getData().getData();    return actionList.stream().collect(Collectors.toMap(ActionDTO::getActionCode, action -> action));}

3.复杂逻辑抽象,业务语义显性化

Programs are meant to be ready by humans and only icidentally for computers to execute.
-- Donald Ervin Knuth 人工智能之父
译:代码是用来让人读的,只是顺便让机器执行而已。
同样的功能语句,或许转化成汇编后是同样的代码,但对于阅读者而言,不同的表述形式,对于理解成本会有非常大的不同。
  
  
    
/** * 工单无ACTION数据 * @param result * @return */private boolean noActionResult(Result<PageWithData<ActionDTO>> result){    if(result == null){        return true;    }    if(!result.isSuccess()){        return true;    }    if(result.getData() == null){        return true;    }    if(CollectionUtils.isEmpty(result.getData().getData())){        return true;    }    return false;}

4.关注点分离,抽象解析器模型

关注点分离(Separation of concerns)[5] 是将计算机程序分隔为不同部分以便分块聚焦与处理的一种设计原则。这个概念最早在1974年,Dijkstra Edsger在他的文章  On the role of scientific thought  [6]中提出的。分离关注点使得解决特定领域问题的程式码从业务逻辑中独立出来,聚焦问题越小复杂程度越低,问题越易解决。
  
  
    
/** * 工单解析工具类 * @author niexiaolong * @date 2022/8/24 */public class CaseParser {
/** * 解析工单「完结」状态结论 * @param actionDTO 工单状态集 * @return 「完结」结论 */ private static String parseCompleteConsequence(ActionDTO actionDTO){ JSONObject action = JSON.parseObject(actionDTO.getMemo()); if(action == null){ return null; } JSONArray actionKeyArray = action.getJSONArray(ACTION_KEY_MEMO); if(actionKeyArray == null || actionKeyArray.isEmpty()){ return null; } for (int i=0; i<actionKeyArray.size(); i++){ JSONObject actionKey = actionKeyArray.getJSONObject(i); if(actionKey != null && actionDataKey.equals(actionKey.getString(CaseCodeConstant.COMPLETED_DESC_CODE))) { return actionKey.getString(ACTION_VALUE); } } return null; }}

5.业务逻辑统一,抽象层次一致性

抽象层次一致性原则(Single Level of Abstration Principle)[7] 是 ThoughtWorks 的总监级咨询师 Neal Ford 在  The Productive Programmer  [8]一书中提出来的概念。SLAP 强调每个方法中的所有代码都处于同一级抽象层次。如果高层次抽象和底层细节杂糅在一起,就会显得代码凌乱,难以理解,从而造成复杂性。
  
  
    
public List<XSpaceCaseDTO> queryCaseList(String aliId, int currentPage, int pageSize) {    // 从xspace获取工单列表信息    List<CaseTaskRelatedDO> caseTaskInfoList = queryCaseListFromXspace(aliId, currentPage, pageSize);    // 获取每个工单的状态详情    Map<Long, CaseActionInfo> caseId2ActionInfoMap = queryCaseId2ActionMap(caseTaskRelatedList);    // 组装工单数据信息    List<XSpaceCaseDTO> xSpaceCaseList = caseTaskConvertor.convert(caseTaskInfoList, caseId2ActionInfoMap);    return xSpaceCaseList;}


Good Smell

最终我们重构后的代码主体逻辑如下,保证程序健壮性的同时,对不同的职责领域进行划分,保持代码的可读性与可维护性,拯救我们的祖传代码~
  
  
    
private CaseActionInfo queryResolveAction(Long caseId) {    // 获取工单状态集合    Map<Integer, ActionDTO> actionMap = queryCaseActionMap(caseId);    if(actionMap == null){        return null;    }    // 优先判断「完结」状态    if(actionMap.containsKey(CaseCodeConstant.COMPLETE_ACTION_CODE)){        ActionDTO completeAction = actionMap.get(CaseCodeConstant.COMPLETE_ACTION_CODE);        String completeConsequence = CaseParseUtils.getCompleteConsequence(completeAction);        return buildCaseActionInfo(CaseCodeConstant.CASE_COMPLETED, completeAction.getOperatorNick(), completeAction.getGmtModified(), completeConsequence);    }    // 其次判断「联系中」状态    if(actionMap.containsKey(CaseCodeConstant.CONTACTED_ACTION_CODE)){        ActionDTO contactAction = actionMap.get(CaseCodeConstant.CONTACTED_ACTION_CODE);        String contactConsequence = CaseParseUtils.getContactedConsequence(contactAction);        return buildCaseActionInfo(CaseCodeConstant.CASE_CONTACTED, contactAction.getOperatorNick(), contactAction.getGmtModified(), contactConsequence);    }    return CaseActionInfo.emptyAction;}


Smell Battle

我们来看最终的代码效果对比。代码重构不需要单独挑一个复杂的模块,挑一个完整的时间,重构应该在日常开发当中,在我们的编码习惯当中。

参考链接:

[1]https://baike.baidu.com/item/圈复杂度/828737
[2]https://deviq.com/design-patterns/guard-clause
[3]https://deviq.com/design-patterns/guard-clause
[4]https://baike.baidu.com/item/敏捷软件开发:原则、模式与实践/2326384
[5]https://baike.baidu.com/item/关注点分离/7515217
[6]https://www.cs.utexas.edu/users/EWD/transcriptions/EWD04xx/EWD447.html
[7]https://www.techyourchance.com/single-level-of-abstraction-principle/
[8]https://nealford.com/books/productiveprogrammer

《开发者评测局》之云效AppStack产品评测征集令


云效AppStack是以应用为中心的云原生应用交付平台,提供对开发者友好的应用编排、环境管理、部署运维、资源管理、应用发布等一站式应用交付能力,帮助企业建立应用持续交付整体解决方案,加速企业云原生与DevOps转型,提升团队研发效能。


免费体验云效AppStack交付平台,撰写评测记录下你与云效AppStack的美好邂逅,就有机会获得cherry机械键盘、云效定制T恤、30元移动话费券等多重好礼!


点击阅读原文查看详情。

登录查看更多
1

相关内容

【CVPR2022】多机器人协同主动建图算法
专知会员服务
46+阅读 · 2022年4月3日
找工作实用书《LeetCode 题解》,262页pdf
专知会员服务
129+阅读 · 2021年12月2日
【经典书】Linux UNIX系统编程手册,1554页pdf
专知会员服务
44+阅读 · 2021年2月20日
【2020新书】如何认真写好的代码和软件,318页pdf
专知会员服务
63+阅读 · 2020年3月26日
100+篇《自监督学习(Self-Supervised Learning)》论文最新合集
专知会员服务
161+阅读 · 2020年3月18日
Transformer文本分类代码
专知会员服务
116+阅读 · 2020年2月3日
单元测试运行原理探究
阿里技术
0+阅读 · 2022年9月26日
10分钟搞定!Golang分布式ID集合
CSDN
0+阅读 · 2022年9月5日
代码圈复杂度治理小结
阿里技术
0+阅读 · 2022年8月16日
用了那么久的Lombok,你知道它的原理么?
阿里技术
0+阅读 · 2022年8月11日
代码重构:面向单元测试
阿里技术
0+阅读 · 2022年7月29日
卓越工程实践之—前端高质量单测
阿里技术
0+阅读 · 2022年7月6日
谈一谈单元测试
阿里技术
0+阅读 · 2022年2月14日
Java单元测试技巧之JSON序列化
阿里技术
0+阅读 · 2021年10月20日
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
0+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
Arxiv
12+阅读 · 2019年2月28日
Arxiv
31+阅读 · 2018年11月13日
VIP会员
相关VIP内容
【CVPR2022】多机器人协同主动建图算法
专知会员服务
46+阅读 · 2022年4月3日
找工作实用书《LeetCode 题解》,262页pdf
专知会员服务
129+阅读 · 2021年12月2日
【经典书】Linux UNIX系统编程手册,1554页pdf
专知会员服务
44+阅读 · 2021年2月20日
【2020新书】如何认真写好的代码和软件,318页pdf
专知会员服务
63+阅读 · 2020年3月26日
100+篇《自监督学习(Self-Supervised Learning)》论文最新合集
专知会员服务
161+阅读 · 2020年3月18日
Transformer文本分类代码
专知会员服务
116+阅读 · 2020年2月3日
相关资讯
单元测试运行原理探究
阿里技术
0+阅读 · 2022年9月26日
10分钟搞定!Golang分布式ID集合
CSDN
0+阅读 · 2022年9月5日
代码圈复杂度治理小结
阿里技术
0+阅读 · 2022年8月16日
用了那么久的Lombok,你知道它的原理么?
阿里技术
0+阅读 · 2022年8月11日
代码重构:面向单元测试
阿里技术
0+阅读 · 2022年7月29日
卓越工程实践之—前端高质量单测
阿里技术
0+阅读 · 2022年7月6日
谈一谈单元测试
阿里技术
0+阅读 · 2022年2月14日
Java单元测试技巧之JSON序列化
阿里技术
0+阅读 · 2021年10月20日
相关基金
国家自然科学基金
0+阅读 · 2015年12月31日
国家自然科学基金
0+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
Top
微信扫码咨询专知VIP会员