关于java:为什么在“if x then return”之后很少使用“else”?

Why is “else” rarely used after “if x then return”?

这种方法:

1
2
3
4
5
6
7
8
boolean containsSmiley(String s) {
    if (s == null) {
        return false;
    }
    else {
        return s.contains(":)");
    }
}

可以等效地写:

1
2
3
4
5
6
7
boolean containsSmiley(String s) {
    if (s == null) {
        return false;
    }

    return s.contains(":)");
}

在我的经验中,第二种形式更常见,尤其是在更复杂的方法中(可能有几个这样的出口点),对于"抛出"和"返回"也是如此。然而,第一种形式可以使代码的条件结构更加明确。有什么理由比另一个更喜欢一个吗?

(相关:一个函数应该只有一个返回语句吗?)


在这种情况下,else将是多余的,并为函数的主代码创建不必要的额外缩进。


根据我的经验,这取决于代码。如果我在"保护"什么,我会:

1
2
3
4
5
if (inputVar.isBad()) {
    return;
}

doThings();

要点很清楚:如果该语句为假,我不希望函数继续。

另一方面,有一些函数有多个选项,在这种情况下,我会这样写:

1
2
3
4
5
6
7
if (inputVar == thingOne) {
    doFirstThing();
} else if (inputVar == secondThing) {
    doSecondThing();
} else {
    doThirdThing();
}

即使可以写为:

1
2
3
4
5
6
7
8
9
10
if (inputVar == thingOne) {
    doFirstThing();
    return;
}
if (inputVar == thingTwo) {
    doSecondThing();
    return;
}
doThingThree();
return;

它实际上是以哪种方式最清楚地显示代码在做什么(不一定是哪一位代码最短或缩进最少)。


这是一个名为guard子句的模式。其思想是预先进行所有检查,以减少嵌套条件以提高可读性。

从链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
double getPayAmount() {
    double result;
    if (_isDead) {
        result = deadAmount();
    } else {
        if (_isSeparated) {
            result = separatedAmount();
        } else {
            if (_isRetired) {
                result = retiredAmount();
            } else {
                result = normalPayAmount();
            }
        }
    }

    return result;
}

使用guard子句,您将看到以下结果:

1
2
3
4
5
6
7
double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();

    return normalPayAmount();
};


你会看到这一切:

1
2
3
4
5
6
if (condition) {
    return var;
}
// by nature, when execution reaches this point, condition can only be false,
// therefore, the else is unnecessary
return other_var;

在大多数情况下,添加else子句不仅是不必要的,而且在很多情况下,它会被编译器优化掉。

想想计算机是如何看待这段代码的(就机器代码而言,为了演示,这里简化为伪代码):

1
2
3
4
5
6
0x00: test [condition]
0x01: if result of test was not true, goto [0x04]
0x02: push [var] onto stack
0x03: goto [0x05]
0x04: push [other_var] onto stack
0x05: return from subroutine

代码(同样,这是一个伪代码,而不是程序集)对if/then/else条件的作用完全相同。

对于许多人来说,拥有一个函数的多个可能的出口点被认为是不好的和/或令人困惑的实践,因为程序员必须考虑通过他的代码的每一条可能的路径。另一种做法是:

1
return (condition) ? var : other_var;

这简化了代码,并且不会创建任何新的出口点。


我喜欢这样写:

1
2
3
boolean containsSmiley(String s) {
    return s != null && s.contains(":)");
}


与任何有关编码样式的"讨论"一样,没有正确的答案。我倾向于应用这些考虑:

  • 代码是否在所有情况下都按预期工作。(最小惊喜原则)

  • 下一个开发人员(我自己或其他人)能否理解它在做什么以及为什么。

  • 关于变化的代码有多脆弱。

  • 是简单的,因为它需要,而不是更多。也就是说,不在工程上或工程下。

  • 一旦我很高兴我已经满足了以上的要求,其余的通常都只是顺其自然。


    我更喜欢第一个选项,因为它更容易被人阅读。

    作为一个类比,比较下面两句话:"如果今天下雨,那就带伞,否则就带太阳镜。""如果今天下雨,那就带伞,带太阳镜"。第一句话对应于问题的第一段代码,第二段代码对应于第二段代码。第一个更清晰易读,不是吗?


    其他人可能已经注意到了这一点,但我建议不要在通常需要字符串的地方使用空值。如果您真的希望检查以防止有人传递空值,则可以使用断言(在开发时)或单元测试(部署):

    1
    2
    3
    4
    boolean containsSmiley(String s) {
        assert s != null :"Quit passing null values, you moron.";
        return s.contains(":)");
    }

    我换了一条经验法则:永远不要。曾经。传递空值,除非外部API调用明确要求它。第二:如果外部方法可能返回空值,请将其替换为合理的非空值(例如空字符串),或者添加一个整洁的检查。我厌倦了重复的if (thing == null)检查。

    但这有点离题。我喜欢在顶部加上简短的条件和保护子句,如果程序流指示ELSE永远不会到达那里,就删除ELSE。


    else是冗余的。另外,一些IDE(Eclipse)和分析工具(可能是findbugs)可能会将其标记为警告或错误,因此在这种情况下,程序员可能会删除它。


    这是宗教上的争论,一天结束的时候,这无关紧要。我甚至认为第一种形式在某些情况下更易于阅读。如果在if-elseif-elseif-else中有大量的代码,那么乍一看,查看默认返回是什么比较容易。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if (s == null) {
        return false;
    }
    else if (s.Contains(":))")) {
        return true;
    }
    else if (s.Contains(":-(")) {
        return false;
    }

    return s.contains(":)");


    奥卡姆的剃刀是这样一个原则:"实体不应超越必要性而成倍增加。"


    if声明正在检查/执行您的合同/期望不接收空值。出于这个原因,我希望看到它与函数的其余部分分离,因为它与您试图完成的实际逻辑没有任何关系(尽管这个例子非常简单)。

    不过,在大多数情况下,我更喜欢代码在其意图中尽可能地明确。如果您可以对您的函数进行重构,使其更易于他人阅读,那么就进行重构。作为一个专业的程序员,你的目标应该是为那些需要在你之后维护你的代码的人编程(包括你自己2年后…)。你能为他们做的任何事都是值得做的。


    因为它更好。你知道你也可以用来创建多个层次的嵌套,但是没有人真正做到这一点。


    因为Eclipse中有一个可选(默认情况下关闭)警告,如果在这种情况下使用了else;)。


    嗯,有些原因只是惯例,但有一个优势,以上述形式…

    在对返回语句进行编码时,通常会将最后一个语句设置为默认返回值。这主要有助于重构——否则,子句往往会被其他结构所包围,或者可能会意外地深入到树中。


    从维护的角度来看,我更喜欢一个出口点而不是多个出口点。可以在一个出口点修改(或修饰)最终结果,而不是在n个出口点修改(或修饰)。


    第二种形式,如果更简单/更短。这并不总是意味着更清楚。我建议你做你认为最清楚的事。我个人会写信。

    1
    2
    3
    static boolean containsSmiley(String s) {
        return s != null && s.contains(":)");
    }

    虽然else是正确的,并且在逻辑和可运行性方面没有任何问题,但是我喜欢避免在函数没有超出if/else范围的返回语句时出现最初的wtf时刻。


    因为这和写下面其中一篇文章一样,它带来了关于程序员意图的证据:

    1
    2
    3
    4
    5
    6
    boolean containsSmiley(String s) {
        if (s == null)   // The curly braces are not even necessary as the if contains only one instruction.
            return false;

        return s.contains(":)");
    }

    甚至这个:

    1
    2
    3
    boolean constainsSMiley(String s) {
        return string.IsNullOrEmpty(s) ? false : s.Contains(":)");
    }

    这两种形式是:

  • 更加优雅;
  • 易于阅读;
  • 对于阅读程序员来说,更瘦、更快。

  • 在我看来,第二个更合理。它更像是一个"默认"动作,就像一个开关。如果它不匹配任何其他出口点,则执行该操作。你真的不需要别的。我会说,如果整个函数只有if和else if,那么这里的else就有意义了,因为它是一个巨大的条件。如果有多个条件和其他函数在其中运行,那么将使用末尾的默认返回。


    我赞成可读性。如果您正在扫描代码屏幕,试图找出代码的作用,那么这对开发人员来说是一个视觉提示。

    …但实际上并不需要,因为我们都对代码进行了很好的注释,对吧?:)


    第一种形式简单地不那么冗长——当您返回一个值时,您会自动离开您所在函数的作用域并返回给调用者,因此只有当if语句的计算结果不为true并且随后返回任何内容时,之后的任何代码才会执行。


    正如你所看到的,不同的人对可读性有不同的看法。有些人认为代码行数越少,代码的可读性就越强。其他人认为第二种形式的对称性使其更具可读性。

    我的看法是,可能两种观点都是正确的…为了那些抱着他们的人。其推论是,您不能编写所有人都认为最可读的代码。因此,最好的建议是遵循您的强制编码标准所规定的操作(如果它对此有任何规定),并且通常使用您的常识。(如果你背负着一些高声叫嚷的白痴,他们坚持认为他的方法是"正确的"……随波逐流。)