在我最初的一次代码审查中,我被告知在所有 switch 语句中都包含一个默认语句是良好的实践。最近我记起了这个建议,但不记得其理由。现在这听起来相当奇怪。
是否总是包括一个默认语句有合理的理由?
这与编程语言相关吗?我不记得当时我在使用哪种语言-也许这适用于某些语言而不适用于其他语言?
在我最初的一次代码审查中,我被告知在所有 switch 语句中都包含一个默认语句是良好的实践。最近我记起了这个建议,但不记得其理由。现在这听起来相当奇怪。
是否总是包括一个默认语句有合理的理由?
这与编程语言相关吗?我不记得当时我在使用哪种语言-也许这适用于某些语言而不适用于其他语言?
在大多数情况下,switch
语句应该始终包含一个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将其初始化为不同的值时,您也可能会看到这种情况。在这种情况下,default需要初始化它,以便在日后访问该变量的代码不会引发错误。
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 检查某些东西时,很明显每个其他的选择都可以忽略
switch(keystroke)
{
case 'w':
// move up
case 'a':
// move left
case 's':
// move down
case 'd':
// move right
// no default really required here
}
enum MyEnum { FOO = 0, BAR = 1 }; MyEnum value = (MyEnum)2;
会创建一个有效的 MyEnum
实例,但它既不等于 FOO
也不等于 BAR
。如果这两个问题中的任何一个会导致你的项目出现错误,那么一个默认情况将帮助你快速找到错误,通常无需使用调试器! - cdgraham没有默认行为时,上下文很重要。如果您只关心少数值该怎么办?以读取游戏按键为例。
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
仅仅是浪费时间,毫无必要地增加了代码的复杂度。
// do nothing
注释可以清楚地表明,如果没有涵盖所有情况,那么这是“可以”的,与其他switch语句不同,在其他switch语句中这将是“不可以”的。 - The Nail在某些情况下,不设置默认情况实际上可能是有益的。
如果你的switch cases是枚举值,那么如果没有默认情况,当你缺少任何一个case时,你可以得到编译器警告。这样,如果将来添加了新的枚举值,并且你忘记为这些值添加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;
}
无论你使用哪种编程语言,我都建议使用默认子句。
问题可能发生,值可能不是你期望的那样。
如果不想包含默认子句,这意味着你相信自己了解可能的值集合。如果值在这个可能的值集合之外,你肯定希望得到通知 - 这肯定是一个错误。
这就是为什么你应该始终使用默认子句并抛出错误的原因,例如在Java中:
switch (myVar) {
case 1: ......; break;
case 2: ......; break;
default: throw new RuntimeException("unreachable");
}
没有必要在异常消息中包含比“不可达”字符串更多的信息;如果这确实发生了,你需要查看源代码和变量值等信息,并且异常堆栈跟踪将包含该行号,因此没有必要浪费时间编写更多的文本到异常消息中。
throw new RuntimeException("myVar invalid " + myVar);
,因为这样可以提供足够的信息让你找出并修复错误,而无需使用调试器和检查变量,如果这是一个难以重现的罕见问题,那么这可能会很困难。 - Chris Dodddefault:
。但更常见的情况是省略了 default
,因为人们认为 myVar
只可能有列出的值中的任何一个,然而在现实生活中我已经数不清有多少次变量具有不同于“可能”的这些值的值,这时我会感激这个异常使我立即看到问题,而不是导致其他更难调试的错误或错误答案(可能在测试中被忽视)。 - Adrian Smith“switch”语句是否总是需要包含一个default子句?不是。通常情况下应该包含一个default子句。
只有存在某些逻辑需求时,例如断言错误条件或提供默认行为时,才有必要包含default子句。因为仅仅为了“保险”而添加default子句是一种无效的编程方法,没有任何价值。这相当于说所有“if”语句都应该包含“else”,这是模拟程序设计的做法。
以下是一个简单的例子,说明在这种情况下没有必要使用default子句:
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");
}
default:
可以表达你的假设。例如,当 sgn==0
时打印 integer
(既非正数也非负数)是否合适?对我来说,阅读这段代码很困难。我会假设在这种情况下应该写 zero
而不是 integer
,并且程序员假设 sgn
只能是 -1 或 +1。如果是这种情况,有了 default:
将允许程序员尽早捕获假设错误并更改代码。 - Adrian Smith我不同意上面Vanwaril所提供的最受欢迎的答案。
任何代码都会增加复杂性。同时,必须对其进行测试和文档编写。因此,如果您可以使用更少的代码进行编程,则始终是好的。我的观点是,我在非穷尽性switch语句中使用默认子句,而在穷尽性switch语句中不使用默认子句。为确保我做得正确,我使用静态代码分析工具。现在让我们深入了解:
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.
还有一件事情,如果我们总是使用default
子句,可能会发生静态代码分析工具无法检测到完整或非完整的开关语句的情况,因为它总是检测default
子句。这非常糟糕,因为如果我们通过添加另一个值来扩展枚举并忘记将其添加到一个开关语句中,我们将不会得到通知。
我认为这取决于编程语言,但在C语言中,如果你在枚举类型上进行切换并处理了每个可能的值,最好不要包括默认情况。这样,如果您稍后添加了一个额外的枚举标签并忘记将其添加到switch语句中,一个能力强的编译器会警告您缺少的情况。