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

ttt*_*ppp 236 default switch-statement

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

  1. 总是包含默认声明是否有合理的理由?

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

Van*_*ril 256

切换案例几乎总是有default案例.

使用a的原因 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
}
Run Code Online (Sandbox Code Playgroud)

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.
}
Run Code Online (Sandbox Code Playgroud)

这是一个过于简化的例子,但重点是有人阅读代码时不应该想知道为什么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
}
Run Code Online (Sandbox Code Playgroud)

  • 关于击键的例子与你刚才所说的其他内容相矛盾.:| 我认为更多的解释是有道理的,例如:你不关心这种情况下的默认值,因为如果按下另一个键你不关心,但如果传入的变量与预期不同,我们会关心. (24认同)
  • 如果错过案例,某些编译器会在启用枚举时发出警告,并且添加默认案例将禁止该警告.鉴于这是我的经验中一个非常常见的错误来源(忘记在添加枚举值后更新某个开关),这似乎是一个省略默认情况的非常好的理由. (12认同)
  • @Andrew WASD在计算机游戏角色移动中很常见.您不需要任何默认操作,因为其他键可以分配给其他交互.在这种开关中,它有利于调用运动功能. (3认同)
  • @virusrocks如果您的条件是枚举,则您仍然可以具有意外的值。例如,如果您向枚举添加新值,而忘记更新开关。另外,在某些语言中,您可以将整数值分配给枚举。在C ++中,列举MyEnum {FOO = 0,BAR = 1}; MyEnum value =(MyEnum)2;`使一个有效的MyEnum实例不等于FOO或BAR。如果这两个问题中的任何一个都会导致您的项目中出现错误,则默认情况下将帮助您非常快速地发现错误,通常无需调试器! (3认同)
  • 同时,如果您正在检查 GET 参数,您可能不希望每次用户将 get url 更改为无效的内容时都会抛出异常:[ (2认同)

Jar*_*lls 48

没有.

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

以读取游戏的按键为例

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

添加:

default: // Do nothing
Run Code Online (Sandbox Code Playgroud)

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

  • 不同意其他评论.添加默认值//除非您不知道代码是如何工作的,否则不会添加任何内容.我可以在没有默认值的情况下查看switch语句,并且知道默认情况下什么都不做.添加杂乱不会使这更明显. (16认同)
  • 好例子.但事实上,添加一个带有简单`// do nothing`注释的默认子句会清楚地表明,如果不是所有情况都被覆盖,那就是'ok',而不是其他那些"不好"的switch语句. (12认同)
  • 编号不只是处理某些情况.它也可以作为文件.通过编写默认值:和评论//不做任何事或其他事情,代码的可读性变得更好. (7认同)
  • 本着答案的精神,您可以删除最后一个中断。 (3认同)
  • @Gil Vegliach 最佳评论奖颁给你。 (3认同)
  • @RobertNoack - 抱歉,我不同意。添加默认值 // 不执行任何操作可以让其他程序员知道您没有意外忘记包含默认情况。不过,我可以通过评论解释不需要默认值。 (2认同)
  • @jybrd 如果您不相信以前的开发人员故意遗漏了默认值,那么您为什么相信他们将其放入是正确的?默认值可能会意外为空,就像完全丢失一样容易。对于如此微不足道的事情,不需要注释。这是代码混乱。编写完整的单元测试来显示您的意图。(只是我的观点) (2认同)
  • @GilVegliach 我很遗憾这被投票并受到赞扬,因为这是非常糟糕的做法。请永远不要这样做。您将最后一个中断留在那里,因为否则您将很容易引入错误。当下一个程序员出现并在列表末尾添加另一个按键进行检查时,它不仅会创建一个错误,而且会创建一个很难捕获的无声错误。也许我误解了,你的观点是忽略中断和默认值是不好的,但我个人不记得默认语句被遗忘的任何错误,并同意这是明显的不必要的代码混乱。 (2认同)

Adr*_*ith 42

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

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

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

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

switch (myVar) {
   case 1: ......; break;
   case 2: ......; break;
   default: throw new RuntimeException("unreachable");
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我更喜欢`抛出新的RuntimeException("myVar无效"+ myVar);`因为这可能会给你足够的信息来找出并修复bug而不必使用调试器和检查变量,如果这很少,这可能很难发生的问题难以复制. (39认同)
  • 如果它不是错误条件,那么就不需要`default:`.但更常见的是没有,因为人们认为`myVar`除了列出的那些之外永远不会有任何价值; 但是,当变量的值不是"可能"的值时,我无法计算我在现实生活中感到惊讶的次数.在那些情况下,我一直感谢异常使我立即看到,而不是在以后引起一些其他错误(更难调试)或错误的答案(可能在测试中被忽略). (4认同)
  • 如果值与其中一种情况不匹配不是错误条件怎么办? (2认同)
  • 我同意阿德里安.努力工作并尽早失败. (2认同)

小智 41

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

如果您的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;
 }
Run Code Online (Sandbox Code Playgroud)

  • 在您的示例中,您可以抛出异常,而不是在switch语句之后返回.通过这种方式,您可以通过编译器进行静态检查,并在发生意外情况时获得明确的错误. (2认同)
  • 我同意。在 Swift 的例子中, switch 必须是详尽的,否则你会得到一个编译器错误!提供默认值只会消除这个有用的错误。另一方面,如果所有情况都得到处理,则提供默认值实际上会引发编译器警告(无法访问的语句)。 (2认同)
  • 刚刚修复了一个错误,如果没有默认值,它会导致编译器发出警告:所以通常切换枚举变量应该没有默认值。 (2认同)

Kur*_*tyn 13

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

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

  • 我曾经在航空电子设备领域工作(也在国防领域)。它充满了愚蠢的要求,对实际安全没有任何贡献。尤其是 Misra 指南(懒于搜索链接)对于降低错误频率和严重性来说充其量是无用的。为了防止误读:我并不是说所有这些要求都是愚蠢的。大多数人都不是。但有些(且不可忽视的比例)是。 (2认同)

Gab*_*abe 12

"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");
}
Run Code Online (Sandbox Code Playgroud)

这相当于:

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");
}
Run Code Online (Sandbox Code Playgroud)

  • @Harper:你的例子属于"断言错误条件"的范畴. (4认同)
  • 我认为`默认:`在这些情况下编纂你的假设.例如,当`sgn == 0`打印`integer`(既不是正数也不是负数)时,它是否正常,或者是一个错误?对我来说,阅读该代码,很难说.我假设你想在这种情况下写`zero`而不是`integer`,并且程序员假设`sgn`只能是-1或+1.如果是这种情况,使用`default:`将允许程序员尽早捕获假设错误并更改代码. (4认同)

Ger*_*zeg 7

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


Oph*_*tan 6

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

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

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

  • 获得意外情况的另一个常见原因是:代码的其他部分可能会在几年后被修改. (3认同)

Chr*_*odd 5

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

  • "每个可能的价值"都不太可能通过枚举来处理.即使未明确定义该特定值,也可以将整数强制转换为枚举.哪个编译器会警告丢失的案例? (5认同)

小智 5

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

任何代码都会增加复杂性。还必须为此进行测试和记录。所以如果你能用更少的代码编程总是好的。我的观点是,我对非详尽的 switch 语句使用默认子句,而对详尽的 switch 语句不使用默认子句。为了确保我做对了,我使用了静态代码分析工具。那么让我们进入细节:

  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
    }
    
    Run Code Online (Sandbox Code Playgroud)

    玩家可以按任何其他键。然后我们不能做任何事情(这可以通过在默认情况下添加注释来显示在代码中)或者它应该例如在屏幕上打印一些东西。这个案例是相关的,因为它可能发生。

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

    public enum GradeSystemType {System1To6, SystemAToD, System0To100}
    
    Run Code Online (Sandbox Code Playgroud)

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

    switch(type)
    {
      case GradeSystemType.System1To6:
        // do something
      case GradeSystemType.SystemAToD:
        // do something
      case GradeSystemType.System0To100:
        // do something
    }
    
    Run Code Online (Sandbox Code Playgroud)

    所以如果我们扩展GradeSystemType例如System1To3静态代码分析工具应该检测到没有 default 子句并且 switch 语句不是详尽的,所以我们保存。

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