开关语句(switch statements)是否应该始终包含默认子句?

356

在我最初的一次代码审查中,我被告知在所有 switch 语句中都包含一个默认语句是良好的实践。最近我记起了这个建议,但不记得其理由。现在这听起来相当奇怪。

  1. 是否总是包括一个默认语句有合理的理由?

  2. 这与编程语言相关吗?我不记得当时我在使用哪种语言-也许这适用于某些语言而不适用于其他语言?


14
这将在很大程度上取决于语言。 - skaffman
22个回答

4

拥有静态检查和动态检查比仅有动态检查更好。这是反对枚举开关默认情况的原因。

省略默认情况使编译器可以在发现未处理的情况时选择性地发出警告或失败。 如果每个情况都是返回或中断 - 如果需要,将其包装在lambda中,有效的默认情况方便地位于开关后面的部分。编译器将坚持认为该部分是可达的,并且必须处理它(因为开关参数可能在物理上是无效值,即使在逻辑上无效)。

const char *name = [trafficLight]() {
    switch (trafficLight) {
        case TrafficLight::Green: return "Green";
        case TrafficLight::Yellow: return "Yellow";
        case TrafficLight::Red: break;
    }
    return "Red";
}();

一些指南要求在所有开关中设置默认值,例如MISRA C

最终默认子句的要求是防御性编程。该子句应采取适当的措施或包含适当的注释,说明为什么不采取任何措施。

动态检查的典型需求是输入处理,广义上来说。如果一个值来自程序控制之外,就不能信任它。

这也是Misra采取极端防御性编程立场的地方,只要无效值在物理上可表示,就必须进行检查,无论程序是否可以被证明是正确的。如果软件需要在硬件错误存在的情况下尽可能可靠,这是有道理的。但正如Ophir Yoktan所说,大多数软件最好不要“处理”错误。后一种做法有时被称为攻击性编程


4

如果您知道switch语句只会有一组明确定义的标签或值,那么可以这样做来覆盖所有情况,这样您将始终获得有效的结果。只需将默认值放在程序/逻辑上最适合处理其他值的标签上即可。

switch(ResponseValue)
{
    default:
    case No:
        return false;
    case Yes;
        return true;
}

这里default后面的冒号还需要吗?或者这样做是否允许一些特殊的语法,使您可以省略它? - Ponkadoodle
冒号是必需的,那是一个笔误,感谢您提醒我。 - deegee

3

在Java中,default case并非必需。根据JLS规范,最多只能存在一个default case,这意味着可以不使用default case。有时候,使用switch语句也要看具体情况。例如,在Java中,下面的switch块不需要default case。

private static void switch1(String name) {
    switch (name) {
    case "Monday":
        System.out.println("Monday");
        break;
    case "Tuesday":
        System.out.println("Tuesday");
        break;
    }
}

但是在以下需要返回字符串的方法中,默认情况很方便,可以避免编译错误。
    private static String switch2(String name) {
    switch (name) {
    case "Monday":
        System.out.println("Monday");
        return name;

    case "Tuesday":
        System.out.println("Tuesday");
        return name;

    default:
        return name;
    }
}

虽然你可以在上述方法中没有默认情况下通过在结尾处添加return语句来避免编译错误,但提供默认情况会使其更易读。


3

这是一种可选的编码“惯例”。是否需要取决于使用情况。我个人认为,如果不需要它,就不应该存在。为什么要包含用户无法使用或接触到的内容呢?

如果情况可能性有限(例如布尔值),那么默认子句是多余的


3

如果开关变量 (switch(variable)) 无法到达默认情况,则根本不需要默认情况。即使我们保留默认情况,它也根本不会执行。这是死代码。


2

如果在 switch 语句中没有默认情况,那么当出现预测不到的情况时,行为可能是不可预测的。因此,最好在语句中包含一个 default 情况。

switch ( x ){
  case 0 : { - - - -}
  case 1 : { - - - -}
}

/* What happens if case 2 arises and there is a pointer
* initialization to be made in the cases . In such a case ,
* we can end up with a NULL dereference */

这样的做法可能会导致像NULL解引用内存泄漏以及其他类型的严重错误。例如,我们假设每个条件都会初始化指针。但是如果default情况确实出现了,而我们没有在这种情况下进行初始化,那么就有可能遇到空指针异常。因此,建议使用default语句,即使它可能是微不足道的。

2

你应该设置一个默认值来捕获未预料到的传入值。

然而,我不同意Adrian Smith所说的,即你的默认错误信息应该是完全无意义的。可能存在一些你没有预料到的未处理情况(这也是意思所在),你的用户最终将看到类似“不可达”这样毫无意义的消息,这在那种情况下对任何人都没有帮助。

例如,你有多少次遇到了完全无意义的BSOD?或者一个致命异常 @ 0x352FBB3C32342?


请记住,不仅开发人员会看到错误消息。我们生活在现实世界中,人们会犯错。 - John Hunt

2

在枚举使用的 switch 中,default case 可能并不必要。当 switch 包含所有值时,default case 将永远不会执行。因此,在这种情况下,它是不必要的。


1

这取决于特定语言中 switch 语句的工作方式,但在大多数语言中,当没有匹配的 case 时,执行会从 switch 语句中掉出而没有警告。想象一下,你期望某些值并在 switch 中处理它们,但是你得到了输入中的另一个值。什么也不会发生,你也不知道发生了什么。如果你在 default 中捕获了 case,你就会知道出了问题。


0

我相信这是相当特定于语言的,对于C++情况下的枚举类类型来说,这只是一个小问题。它似乎比传统的C枚举更安全。但是

如果你看一下std::byte的实现,它是这样的:

enum class byte : unsigned char {} ;

来源:https://en.cppreference.com/w/cpp/language/enum

还要考虑以下内容:

否则,如果 T 是具有作用域或固定底层类型的枚举类型,并且如果花括号初始化列表只有一个初始化程序,并且如果从初始化程序到底层类型的转换是非收窄的,并且如果初始化是直接列表初始化,则枚举将使用将初始化程序转换为其底层类型的结果进行初始化。

(自 C++17 起)

来源:https://en.cppreference.com/w/cpp/language/list_initialization

这是一个表示未定义枚举器值的枚举类的示例。因此,您不能完全信任枚举。根据应用程序,这可能很重要。

然而,我真的很喜欢 @Harlan Kassler 在他的帖子中所说的话,并将在某些情况下开始使用该策略。

只是一个不安全的枚举类的例子:

enum class Numbers : unsigned
{
    One = 1u,
    Two = 2u
};

int main()
{
    Numbers zero{ 0u };
    return 0;
}

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