找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

697

积分

0

好友

87

主题
发表于 7 小时前 | 查看: 0| 回复: 0

在业务系统中,我们常常会遇到一类“历史悠久”的方法:规则繁多、条件复杂、if 语句层层嵌套。虽然功能上能正确运行,但维护成本极高,一旦业务规则需要调整,修改起来如履薄冰,生怕改出新的问题。

下面这段计算产品提成的 Java 代码,就是一个非常典型的例子:

private List<CmsResult> productCommissionCalculate(List<CmsConfigVo> configVos, List<CmsResult> resultList){
    for (CmsResult cmsResult : resultList){
        BigDecimal newCommission = BigDecimal.ZERO; // 新品提成
        BigDecimal oldCommission = BigDecimal.ZERO; // 老品提成
        BigDecimal inheritedCommission = BigDecimal.ZERO; // 承接品提成
        BigDecimal teamCommission = BigDecimal.ZERO; // 团队提成

        // 运营人员
        Long operatorUserId = cmsResult.getOperatorUserId();

        for (CmsConfigVo configVo : configVos){
            if (configVo.getConfigType().equals(CmsProductType.NEW_PROD.getDbValue())) {
                // 分割字符串并判断是否包含
                if(StringUtils.isNotBlank(configVo.getUserIds())){

                    String[] userIds = configVo.getUserIds().split(",");
                    // 需要判断是否匹配该人员
                    for (String userId : userIds) {
                        if (StringUtils.isNotBlank(userId) && userId.equals(String.valueOf(operatorUserId))) {

                            // 判断总销售额在哪个区间内,这里有2种情况:1.区间内 2.区间外(比如:50w以上的getProfitRangeMax的值是0)
                            // 情况1
                            if (cmsResult.getTotalSales().compareTo(configVo.getProfitRangeMin()) >= 0 &&
                                cmsResult.getTotalSales().compareTo(configVo.getProfitRangeMax()) <= 0){
                                BigDecimal rote = configVo.getCommissionRate().divide(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP);
                                newCommission = cmsResult.getNewProductProfit().multiply(rote);
                            }
                            // 情况2
                            if(cmsResult.getTotalSales().compareTo(configVo.getProfitRangeMin()) > 0 &&
                                configVo.getProfitRangeMin().compareTo(BigDecimal.ZERO) == 0){
                                BigDecimal rote = configVo.getCommissionRate().divide(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP);
                                newCommission = cmsResult.getNewProductProfit().multiply(rote);
                            }
                            cmsResult.setNewCommission(newCommission);
                        }
                    }
                }else{
                    // todo 没有匹配的才会用通用的计算逻辑
                }
            }
        }
    }
    return resultList;
}

这段代码堪称“教科书式”的坏味道:复杂的业务规则、层层嵌套的 if 判断、字符串解析以及 BigDecimal 的精确计算混杂在一起。虽然当前能跑出正确结果,但从 代码规范 和维护性的角度看,已经到了必须重构的地步。

一、当前代码存在的主要问题

1. 多层嵌套导致认知负担过重

代码的结构可以抽象为:

for (CmsResult cmsResult)
  for (CmsConfigVo configVo)
    if (configType)
      if (userIds)
        for (userId)
          if (equals)
            if (区间1)
            if (区间2)

理解这段代码的逻辑,花费在梳理结构上的精力,甚至可能超过了理解业务本身。这种深层嵌套严重违反了单一职责原则,让阅读和维护的成本变得非常高。

2. 区间判断逻辑重复且不直观

原代码中使用了两种条件来判断销售额区间:

// 情况1
if (totalSales >= min && totalSales <= max)

// 情况2
if (totalSales > min && min == 0)

这种分散且带有魔术数字(0)的判断,语义不清晰,非常容易在后续修改中被破坏。业务规则的表达应该集中且自解释。

3. 核心计算逻辑重复

提成的计算逻辑 rate / 100 * profit 在代码中至少出现了两次,并且可以预见,在后续添加老品、承接品提成时,这段相同的代码会被复制粘贴更多次。这种重复是滋生 Bug 和增加维护难度的温床。

二、优化的核心原则

对于这类遗留系统的复杂业务方法,一个稳妥有效的优化原则是:拆解条件、提前返回(或 continue)、方法语义化。通过将复杂的条件判断和具体操作拆分成具有明确命名的小方法,可以大幅提升代码的可读性和可维护性。

三、分步骤实战优化

接下来,我们一步步应用上述原则,对原始代码进行重构。

1. 理顺最外层结构,使用 continue 提前过滤

首先,我们把最外层的循环结构整理清晰,将不符合条件的配置提前跳过。

for (CmsResult cmsResult : resultList) {

    BigDecimal newCommission = BigDecimal.ZERO;
    // ... 其他提成初始化

    Long operatorUserId = cmsResult.getOperatorUserId();

    for (CmsConfigVo configVo : configVos) {

        // 只处理新品配置
        if (!CmsProductType.NEW_PROD.getDbValue().equals(configVo.getConfigType())) {
            continue;
        }

        // 处理专属人员提成
        if (hasExclusiveUser(configVo)) {
            if (!matchUser(configVo, operatorUserId)) {
                continue;
            }

            if (!inSalesRange(cmsResult.getTotalSales(), configVo)) {
                continue;
            }

            newCommission = calculateCommission(
                    cmsResult.getNewProductProfit(),
                    configVo.getCommissionRate()
            );
        } else {
            // todo 通用提成逻辑
        }
    }

    cmsResult.setNewCommission(newCommission);
}

优化后,主循环的逻辑变得一目了然:if-continue 结构清晰地表达了过滤步骤,成功条件汇聚到最后进行计算,结构扁平,易于理解。

2. 抽取“是否有专属人员”的判断

这是一个简单的状态判断,抽取成方法使其意图更明确。

private boolean hasExclusiveUser(CmsConfigVo configVo) {
    return StringUtils.isNotBlank(configVo.getUserIds());
}

3. 抽取“用户ID匹配”逻辑

将遍历数组匹配用户ID的逻辑独立出来,消除内层的 for-if 嵌套。

private boolean matchUser(CmsConfigVo configVo, Long operatorUserId) {
    String[] userIds = configVo.getUserIds().split(",");
    String currentUserId = String.valueOf(operatorUserId);

    for (String userId : userIds) {
        if (currentUserId.equals(userId)) {
            return true;
        }
    }
    return false;
}

如果项目允许使用 Java 8 或更高版本,可以用更简洁的 Stream API 来实现:

return Arrays.stream(configVo.getUserIds().split(","))
        .anyMatch(id -> id.equals(String.valueOf(operatorUserId)));

4. 统一封装销售额区间判断(关键步骤)

这是优化中最有价值的一步。我们将分散且晦涩的区间判断逻辑,统一收口到一个语义清晰的方法中。

private boolean inSalesRange(BigDecimal totalSales, CmsConfigVo configVo) {

    BigDecimal min = configVo.getProfitRangeMin();
    BigDecimal max = configVo.getProfitRangeMax();

    // max == 0 表示无上限
    if (BigDecimal.ZERO.compareTo(max) == 0) {
        return totalSales.compareTo(min) >= 0;
    }

    return totalSales.compareTo(min) >= 0
        && totalSales.compareTo(max) <= 0;
}

现在,任何关于销售额区间规则的修改,都只需要改动这一个方法,彻底解决了逻辑分散的问题。

5. 统一提成计算方法

将重复的提成计算逻辑提取成通用方法,为后续计算其他类型提成做好准备。

private BigDecimal calculateCommission(BigDecimal profit, BigDecimal rate) {
    if (profit == null || rate == null) {
        return BigDecimal.ZERO;
    }
    BigDecimal realRate = rate
            .divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
    return profit.multiply(realRate);
}

总结

经过以上五个步骤的重构,原始的“意大利面条式”代码被转化为了结构清晰、职责分明的模块化代码。每个方法只做一件事,并且都有一个名副其实的名字。当业务规则再次变化时,我们只需要修改或替换对应的那个小方法即可,比如修改 inSalesRangecalculateCommission,而不会牵一发而动全身。

复杂的业务逻辑本身并不可怕,可怕的是将所有的复杂性都堆砌在一个方法里。通过运用“拆解、提取、语义化”这套组合拳,即使是历史遗留的复杂代码,也能被有效地驯服和优化。这种重构思路,也深刻体现了良好的 设计模式 与编程思想,值得在更多 业务逻辑优化 场景中实践。




上一篇:TCP与UDP协议对比:用“快递公司”和“挑山工”讲透传输层核心差异
下一篇:深入理解TCP协议:三次握手、四次挥手与状态机详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 16:15 , Processed in 0.231744 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表