在我的第一次代码评审中(不久前),有人告诉我,在所有switch语句中包含一个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. 处理“默认”操作,其中的case是针对特殊行为的。

您在菜单驱动程序和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.
}

这是一个过于简化的例子,但关键是阅读代码的人不应该怀疑为什么变量不能是1或2以外的东西。


我能想到的唯一不使用默认值的情况是,当开关正在检查一些非常明显的东西时,其他选项都可以愉快地忽略

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

其他回答

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

因此,我认为如果不应该达到默认值—您不必添加它。

注意,“不应该达到”意味着如果达到了,这是软件中的一个错误——你确实需要测试可能包含不需要的值的值,因为用户输入等等。

我不同意上面Vanwaril投票最多的回答。

任何代码都会增加复杂性。此外,还必须为此进行测试和文档编制。所以用更少的代码编程总是好的。我的观点是,我对非穷举switch语句使用default子句,而对穷举switch语句不使用default子句。为了确保我做对了,我使用了静态代码分析工具。让我们来详细了解一下:

Nonexhaustive switch statements: Those should always have a default value. As the name suggests those are statements which do not cover all possible values. This also might not be possible, e.g. a switch statement on an integer value or on a String. Here I would like to use the example of Vanwaril (It should be mentioned that I think he used this example to make a wrong suggestion. I use it here to state the opposite --> Use a default statement): switch(keystroke) { case 'w': // move up case 'a': // move left case 's': // move down case 'd': // move right default: // cover all other values of the non-exhaustive switch statement } The player could press any other key. Then we could not do anything (this can be shown in the code just by adding a comment to the default case) or it should for example print something on the screen. This case is relevant as it may happen. Exhaustive switch statements: Those switch statements cover all possible values, e.g. a switch statement on an enumeration of grade system types. When developing code the first time it is easy to cover all values. However, as we are humans there is a small chance to forget some. Additionally if you add an enum value later such that all switch statements have to be adapted to make them exhaustive again opens the path to error hell. The simple solution is a static code analysis tool. The tool should check all switch statements and check if they are exhaustive or if they have a default value. Here an example for an exhaustive switch statement. First we need an enum: public enum GradeSystemType {System1To6, SystemAToD, System0To100} Then we need a variable of this enum like GradeSystemType type = .... An exhaustive switch statement would then look like this: switch(type) { case GradeSystemType.System1To6: // do something case GradeSystemType.SystemAToD: // do something case GradeSystemType.System0To100: // do something } So if we extend the GradeSystemType by for example System1To3 the static code analysis tool should detect that there is no default clause and the switch statement is not exhaustive so we are save.

还有一件事。如果我们总是使用默认子句,那么静态代码分析工具可能无法检测穷尽性或非穷尽性switch语句,因为它总是检测到默认子句。这是非常糟糕的,因为如果我们将枚举扩展为另一个值,并且忘记将其添加到一个switch语句中,我们将不会得到通知。

“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");
}

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

一些(过时的)指南是这么说的,比如MISRA C:

最后一个默认子句的要求是防御性编程。该条款应采取适当的行动,或包含关于为什么不采取行动的适当评论。

这个建议已经过时了,因为它不是基于目前的相关标准。明显的疏漏是哈兰·卡斯勒说的

省略默认大小写允许编译器在看到未处理的大小写时选择性地发出警告或失败。静态可验证性毕竟比任何动态检查都要好,因此当您也需要动态检查时,这种牺牲是不值得的。

正如Harlan还演示的那样,在切换之后可以重新创建与默认情况相同的功能。当每个情况都是早期返回时,这是微不足道的。

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

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