15

处理乍一看需要许多嵌套 if 语句的复杂业务逻辑的好方法是什么?

例子:

优惠券。可能:

1a) 价值折扣
1b) 百分比折扣

2a) 正常折扣
2b) 累进折扣

3a) 需要访问优惠券
3b) 不需要访问优惠券

4a) 仅适用于之前已购买的客户
4b) 适用于任何客户

5a) 仅适用于来自国家 (X,Y,…) 的客户

这需要比这更复杂的代码:

if (discount.isPercentage) {
    if (discount.isNormal) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    } else if (discount.isProgressive) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    }
} else if (discount.isValue) {
    if (discount.isNormal) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    } else if (discount.isProgressive) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    }
} else if (discount.isXXX) {
    if (discount.isNormal) {
    } else if (discount.isProgressive) {
    }
}

即使您将 IF 替换为 switch/case,它仍然太复杂了。有什么方法可以使它可读、可维护、更易测试和易于理解?

4

9 回答 9

14

好问题。“条件复杂性”是一种代码味道。多态是你的朋友。

条件逻辑在其初期是无辜的,因为它很容易理解并包含在几行代码中。不幸的是,它很少老化。你实现了几个新特性,突然你的条件逻辑变得复杂和广泛。[Joshua Kerevsky:重构为模式]

为了避免嵌套 if 块,您可以做的最简单的事情之一就是学习使用保护子句

double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
};  

我发现的另一件事可以很好地简化事情,并使您的代码自我记录,是Consolidating conditionals

double disabilityAmount() {
    if (isNotEligableForDisability()) return 0;
    // compute the disability amount

与条件表达式相关的其他有价值的重构技术包括Decompose ConditionalReplace Conditional with VisitorReverse Conditional

于 2009-10-22T13:43:41.663 回答
10

规范模式可能是您正在寻找的。

概括:

在计算机编程中,规范模式是一种特定的软件设计模式,通过使用布尔逻辑将业务逻辑链接在一起,可以重新组合业务逻辑。

于 2009-10-22T13:38:59.817 回答
6

我会编写一个通用状态机,它以要比较的事物列表为食。

于 2009-10-22T13:39:02.337 回答
2

面向对象的做法是让多个折扣类实现一个通用接口:

dicsount.apply(order)

将用于确定订单是否符合折扣类别中的折扣的逻辑。

于 2009-10-22T13:39:33.027 回答
2

你真的应该看到


清洁代码讲座 - Miško Hevery 的继承、多态性和测试

Google 技术讲座 2008 年 11 月 20 日

抽象的

您的代码是否充满了 if 语句?切换语句?你在不同的地方有相同的 switch 语句吗?当您进行更改时,您是否发现自己在多个地方对相同的 if/switch 进行了相同的更改?你有没有忘记一个?

本演讲将讨论使用面向对象技术删除许多条件的方法。结果是更干净、更紧凑、设计更好的代码,更容易测试、理解和维护。

于 2011-03-25T18:34:54.877 回答
1

使用保护条款可能会有所帮助。

于 2009-10-22T13:42:34.280 回答
1

FWIW,我已经非常成功地将Hamcrest用于此类事情。我相信你可以说它实现了规范模式,@Arnis 谈到了。

于 2009-10-22T14:03:32.480 回答
0

我的第一个想法是这是不可测试的,这导致我找到一个解决方案,以使其可测试。

if (discount.isPercentage) {
  callFunctionOne(...);
} else if (discount.isValue) {
  callFunctionThree(...);
} else if (discount.isXXX) {
  callFunctionTwo(...);
}

然后你可以让每个嵌套的 if 语句成为一个单独的调用。通过这种方式,您可以单独测试它们,并且当您测试大组时,您知道每个人都可以工作。

于 2009-10-22T13:41:04.063 回答
0

制作检查特定情况的方法。

bool IsValueNormalAndRequiresCoopon(折扣折扣){...}

bool IsValueNormalAndRequiresCoupon(折扣折扣){...}

ETC

一旦你开始这样做,你就可以更容易地看到你可以在哪里抽象出选择之间的共同逻辑。然后你可以从那里去。

对于复杂的决策,我经常会得到一个处理可能状态的类。

于 2009-10-22T13:42:18.077 回答