重构复杂if表达式的最佳方式

Best way to refactor complex if expression

本文关键字:最佳 方式 表达式 复杂 if 重构      更新时间:2023-09-26

我刚刚加入了一个大型电子商务项目,该项目使用了angularJS前端。我的任务是在结帐页面上添加许多复杂的功能。其中已经有很多复杂的逻辑。

为了让事情变得更难,我不断遇到许多if语句表达式,就像下面的一个,这使得它很难理解,这是一个缓慢的过程,通过许多这些类型的if表达式。

其中一些表达非常关键,有时甚至更长…没有单元测试,当我问其他开发人员这是在检查什么和为什么(只是为了确保我理解),我通常被指向别人,而不是一个解释。

if ((!previousBillingAddress) || (previousBillingAddress && previousBillingAddress.id !== bag.paymentAddress.id)){
    console.log('all of the above was true'); // just a dummy log
} else {
    console.log('all of the above was false'); // just a dummy log
}

有没有人有好的技巧来重构这些类型的表达式?

我想把它们分解成具有描述性名称的函数,函数可以返回truefalse,但我不确定是否有更好的方法。

A = previousBillingAddress 
B = previousBillingAddress.id !== bag.paymentAddress.id

则表达式为:

if (!A || (A && B)) {
  log1
} else {
  log2
}

我们可以用!A || (A && B)做什么?它等于!A || B:

A   |  B  |  !A  |  A && B  |  !A || (A && B)  |  !A || B
==========================================================
1   |  1  |   0  |    1     |        1         |     1
1   |  0  |   0  |    0     |        0         |     0
0   |  1  |   1  |    0     |        1         |     1
0   |  0  |   1  |    0     |        1         |     1

这就是为什么表达式等于:

if (!previousBillingAddress || previousBillingAddress.id !== bag.paymentAddress.id) {
    console.log('all of the above was true'); // just a dummy log
} else {
    console.log('all of the above was false'); // just a dummy log
}

TL;博士

上表仅检查!A || (A && B)是否等于!A || B。如何猜出!A || B ?对于这样的表达式,最好遵循以下规则:

A == !(!(A))                        (rule 1)
!(A && B) == !A || !B               (rule 2)
!(A || B) == !A && !B               (rule 3)
A && (B || C) == A && B || A && C   (rule 4)

我们有!A || (A && B),开始玩。根据规则1,它等于

!(!(!A || (A && B)))

现在我们使用规则3:

!(!(!A || (A && B))) == !(A && !( A && B))
规则2:

!(A && !( A && B)) == !(A && (!A || !B))           (*)

由于规则4:

A && (!A || !B) == (A && !A) || (A && !B) 

我们有(A && !A) || (A && !B),它可以减少到(A && !B)。现在我们可以回到(*),我们有:

!(A && (!A || !B)) == !((A && !A) || (A && !B)) == !(A && !B)
根据规则2,我们得到:
!(A && !B) == !A || B

您可以删除previousBillingAddress &&部分-在||的第二个操作数中,您已经确定previousBillingAddress不是假的。这将使总体条件

if (!previousBillingAddress || previousBillingAddress.id !== bag.paymentAddress.id) {
    console.log('all of the above was true'); // just a dummy log
} else {
    console.log('all of the above was false'); // just a dummy log
}

对我来说足够短了。如果没有,做一个适当命名的辅助函数,你传递previousBillingAddressbag .

在我个人看来,我认为你可以重构和封装函数内部的验证/检查,然后如果相同的验证适用于你的代码(文件/模块等)的其他部分,你可以重用它。在这种情况下,你使用的是angular,所以最好使用angular自定义服务来进行这种类型的业务验证,因为它们也可以操纵作用域

注释是我们最好的朋友。您希望确保下一个查看您代码的人知道确切地知道它应该做什么,并且从那里可以更快更容易地看到确切地看到它在做什么

在这种情况下,if语句中的表达式可以缩短。下面是我用伪代码的方式注释它的方式:

// we need to check if the user has, and is using, a previous billing address
if (previousBillingAddress && previousBillingAddress.id === bag.paymentAddress.id)){
    // the user is using a previous billing address
    // do some work with the previous billing address
} else {
    // the user is using a new address
    // do some work with the new address
    // save the new billing address
}

注意previousBillingAddress是这个if语句的主体。如果我们正在检查previousBillingAddress,那么在我们的第一个块中,我们要确保我们处理了如果我们有一个以前的账单地址会发生什么。这样更合乎逻辑。

如果我们没有以前的账单地址,那么检查我们是否有以前的账单地址就没有意义了,如果我们有,我们该怎么办。

看这是多么干净和合乎逻辑!& lt; 3

我建议将复杂的条件提升为布尔变量(或者常量,如果你支持的JS级别允许你使用const)。例如:

var weakPrevBillingAddress = 
      !previousBillingAddress || 
      (previousBillingAddress && 
        (previousBillingAddress.id !== bag.paymentAddress.id));
if (weakPrevBillingAddress) {
  console.log('judged weak previousBillingAddress');
  // other processing expected here
} else {
  console.log('judged strong previousBillingAddress');
  // other processing expected here
}

正如您和philip yoo所建议的,您可以将条件提升为布尔帮助函数。但是,大多数定义此类函数的方法将使逻辑远离使用点,从而使理解变得更加困难。布尔变量(布尔变量)"谓词")可以很容易地定义接近他们的使用(例如,在你的if语句之前)。所以没有太多额外的距离,但是它们仍然把布尔值的计算和它的使用分开。单独关注计算的能力可以使事情变得更简单、更容易理解。如上所示,它还允许您分割长线条和缩进使用更好的清晰度。

我不知道你是否有重命名现有变量名的灵活性。如果你这样做,你正在使用的是非常长的。长名字有助于增进理解,但每行重复几次的22个字符的名字会分散注意力,而不是清晰。较短的名称(用于源值或仅用于您计算的谓词)会有所帮助。例如:

var weakPrevBA = !prevBA || 
      (prevBA.id !== bag.paymentAddress.id);

选择好的变量名是一门艺术,开发人员对"多少描述才足够"的品味各不相同。但在一个一直处理账单地址的应用程序中,一行接一行地拼写billingAddress并不一定有帮助。如果您正在阅读代码和注释,您可能会高度意识到您正在处理帐单地址。如果没有,请添加一个快速注释,而不是每行多次拼写完整的概念。

注意,在这个较短的示例中,我还简化了布尔表达式。(这也是Bergi提出的建议)这是个好主意——只要你确定它可以被正确地简化。但是,这样做必须非常小心。您没有单元测试,因此您没有简单的方法来测试简化是否真的相同。而且你正在处理复杂的代码。我建议保留原始的布尔表达式,至少在开始时(可能使用缩短的名称)。至少在您更全面地理解代码之前,这减少了bug可能出现的地方。你对代码理解得越好,你做得越简单(例如,通过在条件中添加计算谓词,而不是长而复杂的表达式),你在重写表达式时就越自由和安全。

像这样

var billingId = previousBillingAddress && previousBillingAddress.id;
if (billingId || billingId === bag.paymentAddress.id){
    console.log('all of the above was false'); // just a dummy log
} else {
    console.log('all of the above was true'); // just a dummy log
}

what I did

  1. 因为你正在使用ifelse,在if语句中使用否定不是一个好的做法。所以我去掉了

如果你只想要反的部分(没有else)

if (!(billingId || billingId === bag.paymentAddress.id)){
    console.log('all of the above was true'); // just a dummy log
}
  • 的,,将返回第一个false值,或最后一个true值。所以billingId等于false或者id。在此基础上,你的第一个if是检查它的"falsey"是否继续,所以你也可以使用相同的变量。