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

时间:2011-01-10 17:13:08

标签: default switch-statement

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

  1. 是否有明智的理由始终包含默认声明?

  2. 这种语言是否依赖?我不记得我当时使用的语言 - 也许这适用于某些语言而不适用于其他语言?

21 个答案:

答案 0 :(得分: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
}

答案 1 :(得分: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

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

答案 2 :(得分:42)

无论您使用何种语言,我都会使用默认子句。

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

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

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

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

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

答案 3 :(得分:37)

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

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

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;
 }

答案 4 :(得分:13)

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

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

答案 5 :(得分: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");
}

答案 6 :(得分:7)

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

答案 7 :(得分:6)

如果 这通常导致代码过于复杂,因为错误处理代码太多。 这种错误处理和检测代码会损害代码的可读性,使维护更加困难,并最终导致比它解决的更多错误。

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

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

答案 8 :(得分:5)

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

答案 9 :(得分:4)

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

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

答案 10 :(得分: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语句,但是提供默认情况会使它更具可读性。

答案 11 :(得分:2)

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

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

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

答案 12 :(得分:2)

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

答案 13 :(得分:2)

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

如果案例可能性有限(即布尔值),那么默认子句是 冗余

答案 14 :(得分: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 case语句,即使它 可能是微不足道的。

答案 15 :(得分:2)

因为MISRA C这样说:

  

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

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

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

答案 16 :(得分:1)

我不同意以上Vanwaril投票最多的答案。

任何代码都会增加复杂性。还必须为此进行测试和记录。因此,如果您可以使用更少的代码进行编程,那总是好的。我的意见是,我对非穷举开关语句使用default子句,而对穷举开关语句不使用default子句。为了确保我做对了,我使用了静态代码分析工具。因此,让我们进入细节:

  1. 非详尽的switch语句:那些应该始终具有默认值。顾名思义,这些语句并不涵盖所有可能的值。这也可能是不可能的,例如在整数值或字符串上的switch语句。在这里,我想使用Vanwaril的示例(应该指出的是,我认为他使用了该示例提出了错误的建议。在这里我用相反的方式来表达->使用默认语句):

    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
    }
    

    玩家可以按其他任何键。然后我们什么也做不了(可以通过在默认情况下添加注释就可以在代码中显示出来),或者应该在屏幕上打印一些内容。这种情况可能会发生。

  2. 详尽的切换语句:这些切换语句涵盖了所有可能的值,例如关于等级系统类型枚举的switch语句。第一次开发代码时,很容易覆盖所有值。但是,由于我们是人类,因此很少有机会忘记一些东西。另外,如果以后添加枚举值,则必须对所有switch语句进行调整以使其再次穷举,这将打开通往错误地狱的路径。简单的解决方案是静态代码分析工具。该工具应检查所有switch语句,并检查它们是否详尽无遗或是否具有默认值。这里是详尽的switch语句的示例。首先我们需要一个枚举:

    public enum GradeSystemType {System1To6, SystemAToD, System0To100}
    

    然后,我们需要一个像GradeSystemType type = ...这样的枚举变量。详尽的switch语句将如下所示:

    switch(type)
    {
      case GradeSystemType.System1To6:
        // do something
      case GradeSystemType.SystemAToD:
        // do something
      case GradeSystemType.System0To100:
        // do something
    }
    

    因此,如果我们将GradeSystemType扩展为例如System1To3,则静态代码分析工具应检测到没有默认子句,并且switch语句不是穷举性,因此我们可以保存。

只是一件事。如果我们始终使用default子句,则可能会出现静态代码分析工具无法检测穷举或非穷举开关语句的情况,因为它始终会检测到default子句。这非常糟糕,因为如果将枚举值扩展为另一个值并忘记将其添加到一个switch语句中,我们将不会得到通知。

答案 17 :(得分:1)

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

答案 18 :(得分:1)

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

答案 19 :(得分:0)

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

答案 20 :(得分:0)

我相信这是特定于语言的,对于C ++而言,枚举类类型的次要点。看起来比传统的C枚举更安全。但是

如果您查看std :: byte的实现,则类似:

enum class byte : unsigned char {} ;

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

还要考虑这一点:

  

否则,如果T是范围或范围内的枚举类型   不受固定基础类型的限制,并且如果braced-init-list具有   只有一个初始化程序,并且如果从初始化程序转换为   基础类型是非变窄的,如果初始化为   direct-list-initialization,然后用初始化枚举   将初始化程序转换为其基础类型的结果。

     

(自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;
}