Code Smell 拯救你的祖传代码第1期-圈复杂度高多层嵌套

简介: ![](https://ata2-img.oss-cn-zhangjiakou.aliyuncs.com/neweditor/34a60c92-1d0d-4b77-9def-511a9d8686c3.png)# 前言[圈复杂度(Cyclomatic complexity)](https://baike.baidu.com/item/%E5%9C%88%E5%A4%8D%E6%9D%82%E5%

前言

圈复杂度(Cyclomatic complexity)是一种代码复杂度的衡量标准,在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)是一种改善嵌套代码的优化方案,将某些要害(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)强调一个类应该只有一个发生变化的原因,只负责一处职责,由Robert C. Martin首次在 Agile Software Development 中提出,并成为面向对象五大设计原则之一。

/**
 * 查询工单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)是将计算机程序分隔为不同部分以便分块聚焦与处理的一种设计原则。这个概念最早在1974年,Dijkstra Edsger在他的文章 [On the role of scientific thought](https://www.cs.utexas.edu/users/EWD/transcriptions/EWD04xx/EWD447.html
) 中提出的。分离关注点使得解决特定领域问题的程式码从业务逻辑中独立出来,聚焦问题越小复杂程度越低,问题越易解决。

/**
 * 工单解析工具类
 * @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)是 ThoughtWorks 的总监级咨询师 Neal Ford 在 The Productive Programmer 一书中提出来的概念。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

我们来看最终的代码效果对比。持续演进,持续重构,祖传代码同样也能重获新生~

目录
相关文章
|
14天前
|
SQL 人工智能 运维
玩家说“充值没到账”,AI 如何从日志里找到真相?——SLS 业务模型与 DataAgent 实战
玩家说“充值没到账”,日志里却只有 deliver_status = failed 和 error_code = BAG_FULL。本文以游戏客服为例,介绍如何通过 SLS 语义层沉淀业务口径,再由 DataAgent 解析问题、查询日志、串联证据,辅助定位原因并生成客服答复草稿。
172 1
|
24天前
|
缓存 资源调度 开发工具
dsh 怎么更新?DeepSeek Harness 更新到最新版三种方式:npx 自动更新、npm update -g、源码 git pull
更新 DeepSeek dsh 分三步:先查当前版本,再按安装方式更新——npx 自动用最新、全局安装用 npm update -g、源码用 git pull 重新构建,最后验证版本号。本文覆盖三种安装方式的更新命令、更新前备份、更新后验证与失败兜底排查。
1770 2
dsh 怎么更新?DeepSeek Harness 更新到最新版三种方式:npx 自动更新、npm update -g、源码 git pull
|
运维 监控 JavaScript
(ARMS-AIOps)一文教你用Attributor算法实现多维下钻分析
常见的AIOps应用路径为:对监控的各种关键性能指标(KPI)进行实时异常检测;对多维指标进行根源分析,快速下钻到异常维度和元素;基于应用拓扑和实时Trace,实现根因定位;结合CMDB、关联等、构建异常根因上下文,帮助快速修复问题。 作为KPI指标, 往往包含了很多维度和元素,最显而易见的则是对每一个维度的元素都进行实时异常检测。 对于维度组合笛卡尔集数量很长的场景, 该方案的成本则有点难以承受
6252 0
|
机器学习/深度学习 监控 Web App开发
SLS机器学习最佳实战:根因分析(一)
通过算法,快速定位到某个宏观异常在微观粒度的具体表现形式,能够更好的帮助运营同学和运维同学分析大量异常,降低问题定位的时间。
13565 0
|
3月前
|
安全 Java 语音技术
阿里云虚拟数字人全栈对接指南:从开通到多端集成(含代码示例)
本文系统讲解阿里云虚拟数字人(DVH)对接全流程,涵盖服务开通、权限配置、形象创建、服务端OpenAPI调用、Web/Android多端SDK集成、实时互动与离线合成实现、错误排查及安全优化。详细拆解2D/3D数字人、云渲染/端渲染、播报/互动场景的选型与对接差异,提供Java/Node.js/前端完整代码示例,帮助开发者快速完成从0到1的数字人应用搭建,同时覆盖成本控制与高可用部署要点。
|
3月前
|
人工智能 安全 测试技术
OpenClaw+GitHub Actions落地AI CI/CD流水线,从零搭建智能自动化代码审查系统,效率大幅升级
在现代软件开发流程中,代码审查(Code Review)是保障代码质量、排查安全漏洞、统一编码规范的核心环节,几乎所有研发团队都会将其纳入开发流程。但传统人工代码审查模式在长期落地过程中,逐渐暴露出效率低下、漏洞遗漏、审查标准不统一等诸多痛点,成为制约研发交付速度与代码质量提升的瓶颈。结合当下AI技术与自动化运维工具,本文将详细讲解如何依托OpenClaw与GitHub Actions搭建一套AI驱动的CI/CD流水线,实现代码审查、安全扫描、用例生成、自动部署全流程自动化。整套方案落地后,可将人工代码审查工作量降低70%,Bug发现率提升50%,原本30分钟的部署流程可压缩至5分钟,全方位优
416 0
|
3月前
|
存储 人工智能 安全
Capability Persistence:Agentic Engineering 缺失的能力记忆层
本文揭示AI Agent工程中长期被忽视的“能力记忆缺失”问题:每次代码评审(Review)产生的执行性信息(如“该Agent在安全逻辑上判断力偏差”)均被系统性丢弃,仅规范性信息被用于更新规则。据此提出两条公理,推导出填补缺口的双闭环架构——在既有知识路径(Knowledge Path)之外,新增能力路径(Capability Path),通过Registry(持久化存储)、Router(任务路由)与Adapter(反馈适配)实现Agent执行能力的跨会话记忆与优化。
|
3月前
|
缓存 自然语言处理 运维
降本65%!DeepSeek V4大模型推理成本优化控制与监控体系搭建指南
随着大模型在企业办公、业务系统、智能客服等场景深度普及,API调用带来的高额推理成本成为众多技术团队面临的核心难题。DeepSeek V4凭借优秀的长上下文能力与推理性能被大量企业选用,但无序的调用、重复请求、长文本超时重试等问题,会让月度账单急剧攀升。本文结合真实企业落地案例,全方位讲解DeepSeek V4推理阶段的成本控制策略,同时基于Prometheus+Grafana搭建可视化监控与告警体系,附带完整可运行代码、配置文件与部署命令。经过实测,整套方案可将月度API成本降低65%,同时优化响应延迟、降低接口错误率,在保障用户体验的前提下实现降本增效,适合所有使用大模型API的技术团队参
416 0
|
8月前
|
人工智能 运维 前端开发
Skill菜单全是英文记不住?改一行配置就行!顺带懒人一键提示词!
本文介绍如何通过修改Claude Code中Skill的`name`和`description`字段(在`SKILL.md`文件内),为英文Skill名添加简明中文说明,实现菜单可视化、智能排序与快速识别。操作简单,一行配置即生效,大幅提升AI编程效率。(239字)
|
9月前
|
机器学习/深度学习 数据采集 人工智能
大模型训练方法与技术术语解释
预训练、微调、RLHF、思维链等技术共同构建大模型核心能力。预训练夯实语言基础,微调适配特定任务,RLHF对齐人类偏好,思维链提升推理,少/零样本实现快速迁移,指令微调增强指令理解,自监督利用海量文本,温度控制生成风格,蒸馏压缩模型规模,缩放定律指引性能增长路径。