如果应该满足所有条件的if-else阶梯-是否应添加多余的final子句?


10

这是我最近正在做的事情。

例:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else if (i > current) {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

通常,if / else阶梯比这要复杂得多...

看到最后一条吗?这是多余的。梯子应该最终能够抓住所有可能的条件。因此可以这样重写:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

这就是我过去编写代码的方式,但是我不喜欢这种风格。我的抱怨是,从代码来看,执行代码的最后部分的条件并不明显。因此,我开始明确编写此条件以使其更加明显。

然而:

  • 明确地写出最后的穷尽条件是我自己的想法,我对自己的想法有不好的经验-通常人们对我大喊大叫,说我在做的事情多么可怕-后来(有时很多)我发现这确实是次优
  • 一个提示,说明这可能不是一个好主意:不适用于Javascript,但在其他语言中,编译器往往会发出警告,甚至有关控件到达函数结尾的错误。提示这样做可能不太流行,或者我做错了。
    • 编译器的抱怨使我有时在注释中写出最终条件,但我想这样做是可怕的,因为注释与代码不同,对代码的实际语义没有影响:
    } else { // i > current
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }

我想念什么吗?还是可以按照我的描述做,还是一个坏主意?


+1为@Christophe的最佳回答是,代码冗余增加了出现缺陷的机会。还有一个效率问题要少得多。为了对此进行扩展,对于有问题的代码示例,我们可以将一系列if-else-if单独编写为if,而没有其他,但是我们通常不会这样做,因为if-else为读者提供了独占选择的概念。而不是一系列独立条件(效率也较低,因为这表示即使在一场比赛之后也应评估所有条件)。
埃里克·艾德

@ErikEidt>,因为这表示,即使在一个匹配项+1之后,也应评估所有条件,除非您从函数返回或从循环“中断”(上例中不是这种情况)。
DarekNędza19年

1
+1,好问题。顺便说一句,您可能会感兴趣,因为存在NaN,该示例并不详尽。
monocell

Answers:


6

两种方法均有效。但是,让我们仔细研究一下优缺点。

对于if像这样的琐碎条件的-chain,实际上并不重要:

  • 对于final else,读者显然可以发现else在什么条件下被触发;
  • 对于final else if,对读者来说显而易见的else是,由于您已涵盖了所有内容,因此不需要其他内容。

但是,有很多if依赖更复杂条件的- 链,可能结合了多个变量的状态,也许带有复杂的逻辑表达式。在这种情况下,它不太明显。这是每种样式的结果:

  • final else:您确定已采用其中一个分支。如果您忘记了一个案例,它将通过最后一个分支,因此在调试过程中,如果选择了最后一个分支并且您期望其他事情,那么您会很快发现。
  • final else if:您需要派生冗余条件来进行编码,这会导致潜在的错误源,并可能导致无法覆盖所有情况。此外,如果您错过了一个案例,将不会执行任何操作,而发现缺少的内容可能会更加困难(例如,如果您希望设置的某些变量保留了先前迭代的值)。

因此,最终的冗余状况是风险的来源。这就是为什么我宁愿建议进入决赛的原因else

编辑:高可靠性编码

如果您在开发时考虑到高可靠性,那么您可能会对另一个变体感兴趣:else if用一个final 完成多余的显式final else以捕获任何意外情况。

这是防御性编码。一些安全规范(例如SEI CERTMISRA)建议使用此功能。一些静态分析工具甚至将此作为系统检查的规则来实现(这可以解释您的编译器警告)。


7
如果在最后的裁员条件之后我添加了另一个总是抛出异常的消息,该消息说“您不应该联系我!”怎么办?-会减轻我的方法的一些问题吗?
gaazkam

3
@gaazkam是的,这是一种非常防御的编码样式。因此,您仍然需要计算最终条件,但是对于容易出错的复杂链,您至少要很快找到。我看到的这个变体的唯一问题是,在明显的情况下,它有点过大了。
Christophe

@gaazkam我已经编辑我的回答也地址您的其他想法在您的评论中提到
克里斯托夫

1
@gaazkam抛出异常是一件好事。如果这样做,请不要忘记在消息中包含意外值。可能很难重现,并且意外值可能提供有关问题根源的线索。
马丁·马特

1
另一种选择是将放入assert决赛else if。但是,您的风格指南可能会有所不同。(assert除非程序员搞砸了,否则a的条件永远不会为假。因此,至少是出于预期目的使用了此功能。但是,有很多人滥用它,以至于许多商店都彻底禁止了它。)
Kevin

5

到目前为止,答案中缺少的问题是哪种故障危害较小。

如果您的逻辑很好,那么您所做的事情并不重要,重要的情况是如果您有错误,将会发生什么。

您可以省略最终条件:即使不是正确的选择,也会执行final选项。

您只需添加最终条件:它不会执行任何选项,这取决于情况,这可能仅表示某些内容无法显示(低伤害),或者可能表示在以后某个时刻出现空引用异常(这可能是调试)痛。)

您添加了最终条件和一个例外:它引发。

您必须决定这些选项中哪一个最好。在开发代码中,我认为这是理所当然的事-第三种情况。但是,我可能会在抛出之前将circle.src设置为错误图像,将circle.alt设置为错误消息,以防万一有人决定稍后关闭断言,这将使其无害地失败。

要考虑的另一件事-您的恢复选项是什么?有时您没有恢复路径。我认为这是最终的例子是阿丽亚娜五号火箭的首次发射。发生未捕获的/ 0(实际上是除法溢出)错误,导致助推器损坏。实际上,当时崩溃的代码毫无用处,而绑带助推器亮起的那一刻就变得毫无意义。一旦他们照亮了轨道或动臂,您将尽力而为,不允许出现错误。(如果由于这个原因使火箭误入歧途,则远程安全人员将其转为钥匙。)


4

我建议assert您在决赛中使用以下两种风格的语句:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else {
        assert i > current
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

或无效代码断言:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else if (i > current) {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    } else {
        assert False, "Unreachable code"
    }
}

代码覆盖率工具通常可以配置为从覆盖率报告中忽略诸如“断言”之类的代码。


通过将条件放入断言中,可以有效地显式地记录分支的条件,但是与注释不同,断言条件实际上可以被检查,并且如果在开发过程中或在生产环境中保持断言处于启用状态,则失败(我通常建议保持断言处于启用状态)如果它们对性能的影响不大)。


1
我不喜欢您的第一个选择,第二个更清楚地表明这是一个不可能的情况。
罗伦·佩希特尔

0

我定义了一个“声明”的宏,该宏评估条件,并且在调试版本中属于调试器。

因此,如果我100%确信三个条件之一必须成立,那么我写

If condition 1 ...
Else if condition 2 .,,
Else if asserted (condition3) ...

这足够清楚地表明一个条件为真,并且不需要为断言额外的分支。


-2

我建议完全避免其他情况。使用if声明一下的代码块应该把手,通过退出功能结束块。

这导致代码非常清晰:

setCircle(circle, i, { current })
{
    if (i == current)
    {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
        return
    }
    if (i < current)
    {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
        return
    }
    if (i > current)
    {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
        return
    }
    throw new Exception("Condition not handled.");
}

if今天的决赛当然是多余的。如果/何时有将来的开发人员重新安排模块,这将变得非常重要。因此,将其保留在其中很有帮助。


1
实际上还不清楚,因为现在我需要考虑执行第一个分支是否会改变第二个测试的结果。再加上毫无意义的多重回报。加上没有条件为真的可能性。
gnasher729

实际上,当我故意希望可以采用多种情况时,我实际上会写这样的语句。它在带有不允许掉线的switch语句的语言中特别有用。我认为,如果选择是互斥的,则应将其写成这样,以使案件很明显是互斥的(使用其他)。而且,如果不是这样写的话,则意味着案例不是互斥的(可能会失败)。
杰里·耶利米

@ gnasher729我完全理解您的反应。我认识一些非常聪明的人,他们最初在“避免其他情况”和“早日归来”方面遇到麻烦。我鼓励您花一些时间来适应和研究这个主意,因为这些主意确实客观地降低了复杂性。提前返回实际上解决了您的第一点(需要考虑分支是否可能更改另一个测试)。而且无论如何,“没有条件成立的可能性”仍然存在;使用这种样式,您将获得明显的异常,而不是隐藏的逻辑缺陷。
John Wu

但是两种样式的圈复杂度不是完全一样吗?即等于条件数量
gaazkam
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.