嵌套的if语句中需要一个else语句

21

我有一个问题,可以简化为以下内容:

parameters: a, b

if (a > 5)
{
    Print("Very well, a > 5");

    if (b > 7)
        Print("Even better, b > 7");
    else
    {
        Print("I don't like your variables");
    }
}
else
{
    Print("I don't like your variables");
}

我想只使用一个else而不是两个,因为它们的代码相同。我的想法是创建一个额外的方法,它将返回组合的truefalse,但这是一种严重的过度设计。
另一个选择是使用goto,但这会使代码更难读和不安全。
有什么方法可以避免多次检查相同的条件,并使其尽可能易读?

3
没有什么“神奇”的办法可以做到这一点。你必须选择其中一种“过度”的选项:创建一个方法,声明一个标志,将其封装在lambda/匿名方法中等等。 - Kirk Woll
我很久以前就想要一种语言特性(在某些语言中),可以帮助解决这个问题,甚至在 Java 诞生之前。但到目前为止,还没有人能够实现这一点。我通常使用一个布尔值。 - ajb
1
@ajb:你如何区分该功能与实际上只想在外部循环中执行一个“else”的区别?您希望统一哪个代码内部的if和else部分?如果我在其中使用switch语句,那么它会起作用吗?如果将内部的if重构为另一个函数,它会起作用吗?如果引发异常会发生什么?如果您需要使用特殊标记标记每个if以指示您希望在外部处理此if的else部分,则这与设置标志相同。 - justhalf
6
您可以通过混淆代码并避免使用goto语句来符合某位大学教授曾经告诉您它很糟糕的想法,或者您可以使用语言中明确定义的特性来表达您的意图。其中一种方式比另一种方式对可读性的损害更严重。教条主义在实用编程中是不适用的。 - James Greenhalgh
1
为什么我的问题被关闭了?它要求解决一个具体的问题。代码可能看起来很普遍,但它是为了简化我的问题;把所有的应用逻辑都放在这里是不合适的。而且 - 一个问题有多个正确答案并不意味着它是基于观点的。我提供解决问题的方法,但我说出了我为什么认为它们不好。这就是为什么我要求解决方案并得到不同的正确答案,这有什么不好吗?请投票重新开放或说明关闭的原因。 - Michał
显示剩余12条评论
16个回答

22
void doILikeYourVariables(int a, int b) {
  if (a > 5) {
    Print("Very well, a > 5");
    if (b > 7) {
      Print("Even better, b > 7");
      return;
    }
  }
  Print("I don't like your variables");
}

1
这是一个选择,但是在内部仍然有return的地方几乎等同于goto。它只是将其转换为return语言。 - Michał
14
如果你习惯使用RAII,那么多个return语句比goto更易读,而且相当安全。 - Chris Drew
1
你说得对,我没有考虑到goto的安全性和可读性。我有时不介意使用goto,但它仍被认为是一种不好的习惯。谢谢 :) - Michał
4
@NothingsImpossible: 由于您在多个答案中发布了您的误解,我将在每个评论中进行解释。在原始问题的代码中,当 a>5 and b<=7 时,在执行 "Very well, a > 5" 后会导致输出 "I don't like your variables"。请注意,此处翻译尽可能保持原文意思且更易理解,无解释和额外内容返回。 - justhalf
1
@Michał 你知道在你的 if (a > 5) 结尾和对应的 else 之前有一个隐藏的 goto 吗?它会跳转到 else 结束后。 - Seph
显示剩余3条评论

15

布尔逻辑 101:

public void test(int a, int b, int c) {
    boolean good = true;
    if (good = good && a > 5) {
        System.out.println("Very well, a > 5");
    }
    if (good = good && b > 7) {
        System.out.println("Even better, b > 7");
    }
    if (good = good && c > 13) {
        System.out.println("Even better, c > 13");
    }
    // Have as many conditions as you need, and then
    if (!good) {
        System.out.println("I don't like your variables");
    }
}

另外一种方式是,如果你需要进行大量的检查 -

enum Tests {
    A_gt_5 {
        @Override
        boolean test(int a, int b, int c) {
            return a > 5;
        }
    },
    B_gt_7 {
        @Override
        boolean test(int a, int b, int c) {
            return b > 7;
        }
    },
    C_gt_13 {
        @Override
        boolean test(int a, int b, int c) {
            return c > 13;
        }
    };

    abstract boolean test (int a, int b, int c);
}

public void test(int a, int b, int c) {
    boolean good = true;
    for ( Tests t : Tests.values() ) {
        good = good && t.test(a, b, c);
        if (!good) {
            break;
        }
    }
    if (!good) {
        System.out.println("I don't like your variables");
    }
}

“// Duplicare possint in infinitum” 你的意思是 等等 吗? - Michał
@Michał - 拉丁语 -> 英语Duplicare possint in infinitum -> 无限复制 - OldCurmudgeon
2
写成 // 无限复制 不是更清晰明了吗? :) - Michał
1
@Michał - 是的 - 但不是那么有趣。我考虑过 add hock(这是一个指 ad hoc 的玩笑)和 etc.ad infinitum,但最终决定让你思考。 :) - OldCurmudgeon
1
所有答案中最好的一个。毫无疑问是最易读的,也不会引起性能问题。+1。虽然谈论的是第一个代码块(小而易读的那个),第二个是Java企业版,但我认为它并不那么相关,因为在我看来,作者是以更概念化的方式提出问题的,这个问题甚至标记了多种语言。 - NothingsImpossible
2
这是不正确的,因为它没有反映原始示例的功能。在a>5b<=7的情况下,原始行为是打印"很好,a > 5",然后打印"我不喜欢你的变量" - KevinZ

3
if (a > 5)
{
    Print("Very well, a > 5");
}
if(a > 5 && b >7)
{
    Print("Even better, b > 7");
}
else
{
    Print("I don't like your variables");
}

或者
bool isEvenBetter = false;
if (a > 5)
{
    Print("Very well, a > 5");
    isEvenBetter = b > 7;
}
if(isEvenBetter)
{
    Print("Even better, b > 7");
}
else
{
    Print("I don't like your variables");
}

2
但我们仍然会两次执行 a > 5。当我使用更复杂的 if 语句时,它变得不高效。 - Michał

3

实际上,在您的情况下,只有一种情况下您会“喜欢”这些变量,那就是当a>5 and b>7时。在这种情况下,您只需要在最内层的if中设置一个标志即可。像这样:

parameters: a, b

boolean good = false;
if (a > 5){
    Print("Very well, a > 5");
    if (b > 7){
        Print("Even better, b > 7");
        good = true;
    }
}
if(!good){
    Print("I don't like your variables");
}

在移除多个else的同时,只需要增加一行代码(和一个变量)即可实现。


附注

我认为在这种情况下使用goto(或类似支持的内容)是可以接受的。

我同意“无限制使用goto”是有害的,因为它会混淆程序流程,但在某些情况下,最好使用goto,就像您描述的那样。

请参见此问题:GOTO still considered harmful?

事实上,如果您考虑一下,引发(可能是自定义的)异常与goto是相同的,因为它会使程序流跳转到某个点(即catchexcept)。

不使用goto的最好原因是什么?那是因为可能有多种方法进入一行代码。这也是为什么一些人不建议使用多个返回值的原因。但在您的情况下,我们确实希望在多个点退出,因此该功能是必需的。

受限制的goto(例如Java中的异常处理,只能将“goto”执行到“catch”行)是好的。

我并不是说您应该使用goto,但我正在解决您说“goto很糟糕”的问题,并同时为答案池做出贡献。


2

您可以改变一个状态:

bool good = a > 5;
if(good)
{
    Print("Very well, a > 5");
    good = b > 7;
    if(good) {
        Print("Even better, b > 7");
    }
}
if( ! good) {
    Print("I don't like your variables");
}

谢谢,我没有想到要使用标志! - Michał
引入一个在多个地方使用的变量,不是一个好的编码风格,在我看来。 - billz
1
我认为只设置一次 good 更易读,我进行了编辑以展示这一点。然而这个也是正确的。 - Michał

2
如何使用带有中断的 do 呢?实际上这是一种巧妙的方式来实现 goto,你可以将其视为由多个 if 语句组成的过滤器,其中如果没有任何一个 if 语句被触发,则默认执行最后一部分。
parameters: a, b

do {
  if (a > 5)
  {
    Print("Very well, a > 5");

    if (b > 7)
    {
        Print("Even better, b > 7");
        break;
    }
  }

  Print("I don't like your variables");
} while (false);

英译中:

编辑 - 关于语言的再利用

许多人反对使用 do while 来解决特定问题的专业用法。主要反对意见似乎是它看起来像一个循环,但实际上并不是一个循环,因此这种构造落入了循环使用的一种怪异谷。换句话说,“这不自然。”

我同意这是一种不常见的用法,确实应该有注释来说明这是一个使用 do while 进行过滤的块,允许在达到决策树分支终点时使用 break 语句。这就是它的真正作用,一个由一系列决策组成的硬编码前向遍历决策树,没有回溯。在决策过程的任何时刻,我们都可以做出决策或者按照默认决策继续执行,包括表示没有任何决策。

可以说,任何需要注释才能理解的源代码都不是好的代码。另一方面,几乎所有编程语言都有一些插入注释的方式,因为在六个月后回来进行更改时,注释源代码非常有帮助。
这种方法的好处在于它创建了一个本地作用域,因此决策过程中需要的变量可以正确地构建和销毁。
在某些方面,它有点像 lambda,我想没有人会反对它,它可以在不支持 lambda 的语言中使用。在另一方面,它有点类似于 try catch。
在网上浏览时,可以找到很多文章,其中有人以与原始设计意图不同的方式使用编程语言,例如这篇关于使用函数式编程风格的 C++的文章或这本关于使用面向对象实践的 C 语言的在线书籍。
所有程序员都有一定的编码风格或语言使用习惯。从源代码审查和阅读他人的源代码中学习使用编程语言的不同方式是一件好事。
这不是像混淆C编程竞赛的入口那样棘手的代码。它非常直接。 编辑:比goto更好吗? 对于这种不寻常的do-while使用,有一个问题,“为什么不只使用goto?”阅读Dijkstra的论文“考虑到有害的Go To语句”以及这个关于论文和goto语句的博客讨论, 我们可以看到,使用带有break语句的循环具有几个良好的特性,而这些特性不是goto及其相关标签的特性。
主要特点,特别是在这个例子中,是单向流动,有明确的开始和结束。移动goto标签不会意外改变程序流程。在函数的其他地方使用goto标签作为跳转的机会,从而创建了原本没有意图的依赖关系,也不存在危险。阅读代码,每个程序员都知道,在break处离开循环,并在离开循环后进入循环关闭后的源代码行。结果是你拥有一个漂亮干净的知识块,可以标记为“弄清楚要打印的文本”。

这很狡猾 - 我不需要创建任何额外的方法、lambda或其他东西来实现我的目标。+1 - Michał
在你的代码中,如果a>5但b<=7,它会不喜欢我的变量,而问题中的示例代码却没有这样做 - 所以现在还不太对。 - NothingsImpossible
1
@NothingsImpossible:由于你在多个答案中发布了你的误解,我会在每个评论中进行解释。在原始问题的代码中,当a>5b<=7时,将在“非常好,a>5”后导致“I don't like your variables”的结果。 - justhalf
@justhalf 等一下,让我把头埋进地里,马上回来。 - NothingsImpossible
1
不要使用令人困惑的结构,比如循环实际上并不是循环。只需使用 goto 即可。 - user1804599

2

看了所有的答案,我会这样写,并保留两个else。没有必要把它复杂化。

parameters: a, b

if (a > 5)
{
    Print("Very well, a > 5");

    if (b > 7)
        Print("Even better, b > 7");
    else
        DontLikeIt();
}
else
{
    DontLikeIt();
}

有一个方法,叫做DontLikeIt(),它会打印出你想要的回应。

你在开玩笑吧!!!两个else都没有被移除,还添加了另一个方法!!!再读一遍问题吧。 :p :p 另外,我不是那个给你点踩的人。 - Not a bug
2
不,我不是在开玩笑。我主张保留两个else语句以提高可读性,并引入额外的方法来最小化代码重复。我的结论是,OP拥有比这个小样本更大的代码库。 - cederlof

1

只是为了好玩!

class VariableLiker {
  private:
    bool isGood_;
  public:
    VariableLiker() : isGood_(false) {}
    void checkA(int a) {
      if (a > 5) {
        Print("Very well, a > 5");
        isGood_ = true;
      }
    }
    void checkB(int b){
      if (isGood_ && b > 7)
        Print("Even better, b > 7");
      else
        Print("I don't like your variables");
    }
};

//...

VariableLiker variableLiker;
variableLiker.checkA(a);
variableLiker.checkB(b);

1
糟糕的API。你的API表明你也可以反转调用检查的顺序,但实际上你不能这样做。 - André
@André,同意,显然一个类有点过度设计了,需要扩展以使其更加健壮。我只是想指出,如果您必须维护状态,那么将状态封装在类中并在类上使用方法可能是有意义的。也许在这种情况下不是很适用,但我认为值得注意。 - Chris Drew

1
另一种不使用break或goto的方法是:

int c = (new[] { a > 5 ,a > 5 && b > 7 }).Count(x=>x);
if (c > 0)
{
    Print("Very well, a > 5 ");
}
if (c > 1)
{
    Print("Even better, b > 7");
}
else
{
    Print("I don't like your variables");
}

1
您的代码有趣的一点是即使它喜欢 a ,但如果 b 不够好,它也会 "不喜欢您的变量"
显而易见,您实际上并不关心代码中有两个 else 语句;您希望避免的是 "不喜欢" 代码的重复。以下代码可以解决问题,并且在您不介意放弃 b 的值时非常有用。
if (a > 5)
    Print("Very well, a > 5");
else
    b = 0;

if (b > 7)
    Print("Even better, b > 7");
else
    Print("I don't like your variables");

如果您需要保留b的值,则可以使用另一个变量。
var evenBetter = (b > 7);
if (a > 5)
    Print("Very well, a > 5");
else
    evenBetter = false;

if (evenBetter)
    Print("Even better, b > 7");
else
    Print("I don't like your variables");

这里有一种版本,不会预先计算b的好处直到需要。当测试代价高昂或可能产生副作用时,这更加优越。它还可以摆脱一个else,如果那真的很重要。;-)
var evenBetter = false;
if (a > 5)
{
    Print("Very well, a > 5");
    evenBetter = (b > 7);
}

if (evenBetter)
    Print("Even better, b > 7");
else
    Print("I don't like your variables");

这种“分离”方法的缺点是,粗心的读者可能会认为第二个if语句与a无关,因此未能注意到当a是好的但b不好时出现了异常情况"don't like your variables"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接