switch语句是否应该包含默认子句?

问题描述 投票:213回答:19

在我的第一次代码审查中(前一段时间),我被告知在所有switch语句中包含一个default子句是一种好习惯。我最近记得这个建议,但不记得理由是什么。这对我来说听起来很奇怪。

  1. 总是包含默认声明是否有合理的理由?
  2. 这种语言是依赖的吗?我不记得当时我用的是什么语言 - 也许这适用于某些语言而不适用于其他语言?
default switch-statement
19个回答
243
投票

切换案例应该几乎总是有一个default案例。

使用default的原因

1.'抓住'意想不到的价值

switch(type)
{
    case 1:
        //something
    case 2:
        //something else
    default:
        // unknown type! based on the language,
        // there should probably be some error-handling
        // here, maybe an exception
}

2.处理“默认”操作,其中案例用于特殊行为。

您在菜单驱动的程序和bash shell脚本中看到了很多。当变量在switch-case之外声明但未初始化时,您可能也会看到这一点,并且每个case都将其初始化为不同的值。这里默认需要初始化它,以便访问变量的行代码不会引发错误。

3.要向某人阅读您已经涵盖该案例的代码。

variable = (variable == "value") ? 1 : 2;
switch(variable)
{
    case 1:
        // something
    case 2:
        // something else
    default:
        // will NOT execute because of the line preceding the switch.
}

这是一个过于简化的例子,但重点是有人阅读代码时不应该想知道为什么variable不能是1或2以外的东西。


我可以想到的唯一一个不使用default的情况是当交换机检查某些东西时,其中很明显的其他替代方案可以被高兴地忽略

switch(keystroke)
{
    case 'w':
        // move up
    case 'a':
        // move left
    case 's':
        // move down
    case 'd':
        // move right
    // no default really required here
}

4
投票

如果你知道switch语句只有一组严格定义的标签或值,那么只需要这样做就可以覆盖基础,这样你就可以获得有效的结果。只需将默认值放在标签上,即可以编程/逻辑方式是其他价值观的最佳处理者。

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

3
投票

至少它在Java中不是强制性的。根据JLS的说法,它说最多可以出现一个默认情况。这意味着没有默认情况可以接受。它有时还取决于您使用switch语句的上下文。例如,在Java中,以下开关块不需要默认情况

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

但是在下面的方法中,它希望返回一个String,默认情况下很方便,以避免编译错误

    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语句来避免上述方法的编译错误而没有默认情况,但是提供默认情况会使它更具可读性。


2
投票

您应该有一个默认值来捕获未预期的值。

但是,我不同意Adrian Smith的说法,你的默认错误信息应该是毫无意义的。可能有一个未经处理的案例,你没有预见(这有点重要),你的用户最终会看到,像“无法访问”这样的消息完全没有意义,并且在这种情况下无助于任何人。

一个很好的例子,你有多少次有一个完全没有意义的BSOD?或致命异常@ 0x352FBB3C32342?


2
投票

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


2
投票

它是一个可选的编码“惯例”。根据用途,是否需要它。我个人认为,如果你不需要它,它不应该在那里。为什么要包含用户不会使用或达不到的内容?

如果案例可能性有限(即布尔值),则默认子句是多余的!


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
投票

因为MISRA C这样说:

最终默认条款的要求是防御性编程。本条款应采取适当行动或包含适当的评论,说明为何不采取任何行动。

也就是说,对于大多数软件,我建议不要使用MISRA C:

  • 这种防御性的编程风格指导doesn't care,只有一些值可能是有效的 - 如果变量在物理上能够接受一个值,即使这将是一个bug,你应该处理它。大多数软件应该更喜欢打印堆栈跟踪而不是“处理”错误(如Ophir Yoktan所述)。
  • 尤其是枚举开关应该没有默认条款(如Harlan Kassler所说)。而且,正如Harlan也明显地证明的那样,处理无效值可以在交换机外部完成 - 这一点在Misra's discussion中缺失。

1
投票

取决于特定语言中的开关如何工作,但是在大多数语言中,如果没有匹配的情况,则执行会在没有警告的情况下通过switch语句进行。想象一下,你期望一些值并在switch中处理它们,但是你在输入中得到了另一个值。什么都没发生,你什么都不知道。如果你在默认情况下发现了这个案例,你就会知道出现了问题。


1
投票

在枚举使用的开关中可能不需要默认情况。当switch包含所有值时,默认情况永远不会执行。所以在这种情况下,没有必要。


0
投票

switch语句是否应该包含默认子句?在没有默认情况下,没有开关情况可以存在,在开关情况下,默认情况下将触发开关值switch(x),在这种情况下x与任何其他情况值不匹配时。


44
投票

没有。

如果没有默认操作,上下文很重要。如果你只关心几个价值观会怎么样?

以读取游戏的按键为例

switch(a)
{
   case 'w':
     // Move Up
     break;
   case 's':
     // Move Down
     break;
   case 'a':
     // Move Left
     break;
   case 'd':
     // Move Right
     break;
}

添加:

default: // Do nothing

只是浪费时间并且无缘无故地增加代码的复杂性。


42
投票

无论你使用什么语言,我都会使用默认子句。

事情可以而且确实出错了。价值观将不是您所期望的,依此类推。

不想包含默认子句意味着您确信您知道可能的值集。如果您认为您知道可能值的集合,那么,如果该值超出了这组可能的值,您将希望被告知它 - 这肯定是一个错误。

这就是为什么你应该总是使用default子句并抛出错误的原因,例如在Java中:

switch (myVar) {
   case 1: ......; break;
   case 2: ......; break;
   default: throw new RuntimeException("unreachable");
}

没有理由包含更多信息而不仅仅是“无法访问”的字符串;如果确实发生了这种情况,那么无论如何你都需要查看变量等的源和值,并且异常堆栈跟踪将包含该行号,因此不需要浪费时间将更多文本写入异常消息。


37
投票

没有默认情况在某些情况下实际上可能是有益的。

如果你的switch case是枚举值,没有默认情况,你可以在没有任何情况下收到编译器警告。这样,如果将来添加新的枚举值并且您忘记在交换机中添加这些值的大小写,您可以在编译时找到有关该问题的信息。如果将无效值强制转换为枚举类型,您仍应确保代码对未处理的值采取适当的操作。因此,对于可以在枚举情况下返回而不是中断的简单情况,这可能最有效。

enum SomeEnum
{
    ENUM_1,
    ENUM_2,
    // More ENUM values may be added in future
};

int foo(SomeEnum value)
{
    switch (value)
    {
    case ENUM_1:
        return 1;
    case ENUM_2:
        return 2;
    }
    // handle invalid values here
    return 0;
 }

13
投票

在我的公司,我们为航空电子和国防市场编写软件,我们总是包含一个默认语句,因为必须明确处理switch语句中的所有情况(即使它只是一个评论说“什么也不做”)。我们无法承受软件只是行为不端或只是意外崩溃(甚至是我们认为不可能的)价值。

可以讨论的是,默认情况并不总是必要的,但是总是要求它,我们的代码分析器可以很容易地检查它。


11
投票

“switch”语句是否应始终包含默认子句?不应该。它通常应该包括默认值。

包含默认子句只有在有事情要做的情况下才有意义,例如断言错误条件或提供默认行为。包括一个“只是因为”是货物崇拜节目并没有提供任何价值。它的“开关”相当于说所有“if”语句都应该包含“else”。

这是一个无关紧要的例子:

void PrintSign(int i)
{
    switch (Math.Sign(i))
    {
    case 1:
        Console.Write("positive ");
        break;
    case -1:
        Console.Write("negative ");
        break;
    default: // useless
    }
    Console.Write("integer");
}

这相当于:

void PrintSign(int i)
{
    int sgn = Math.Sign(i);
    if (sgn == 1)
        Console.Write("positive ");
    else if (sgn == -1)
        Console.Write("negative ");
    else // also useless
    {
    }
    Console.Write("integer");
}

7
投票

据我所知,答案是“默认”是可选的,说开关必须始终包含默认值,就像说每个'if-elseif'必须包含'else'。如果默认情况下要执行逻辑,那么'default'语句应该在那里,否则代码可以继续执行而不做任何事情。


6
投票

在不需要时使用默认子句是Defensive programming这通常导致代码过于复杂,因为错误处理代码太多。这种错误处理和检测代码损害了代码的可读性,使维护更加困难,并最终导致比它解决的更多错误。

所以我相信如果不应该达到默认值 - 你不必添加它。

请注意,“不应该达到”意味着如果它达到它是软件中的错误 - 您需要测试由于用户输入等可能包含不需要的值的值。


5
投票

我会说这取决于语言,但在C中如果你打开一个枚举类型并且你处理每一个可能的值,你最好不要包括一个默认情况。这样,如果您稍后添加额外的枚举标记并忘记将其添加到交换机,那么合格的编译器会向您发出有关丢失案例的警告。

© www.soinside.com 2019 - 2024. All rights reserved.