Switch Statement Fallthrough…should it be allowed?
只要我记得,我就避免使用switch语句fallthrough。事实上,我不记得它曾经进入我的意识,作为一种可能的方式去做事情,因为它在我的头脑中被钻了一个早期,它只是一个错误,在开关声明。然而,今天我遇到了一些设计时使用它的代码,这让我立刻想知道社区中每个人对switch语句的理解是什么。
它是编程语言不应该明确允许的东西(如C虽然它提供了一个解决方案),还是任何语言的一个功能,足以让程序员掌握?
编辑:我不太明白我所说的"失败"是什么意思。我经常使用这种类型:
1 2 3 4 5 6 7 8 9 10 11 | switch(m_loadAnimSubCt){ case 0: case 1: // Do something break; case 2: case 3: case 4: // Do something break; } |
不过,我很担心这样的事情。
1 2 3 4 5 6 7 8 9 10 11 | switch(m_loadAnimSubCt){ case 0: case 1: // Do something but fall through to the other cases // after doing it. case 2: case 3: case 4: // Do something else. break; } |
这样,只要case为0,1就会在switch语句中执行所有操作。我已经从设计中看到了这一点,我只是不知道我是否同意应该这样使用switch语句。我认为第一个代码示例是非常有用和安全的。第二个看起来有点危险。
这可能取决于你认为的失败。我对这类事情没意见:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | switch (value) { case 0: result = ZERO_DIGIT; break; case 1: case 3: case 5: case 7: case 9: result = ODD_DIGIT; break; case 2: case 4: case 6: case 8: result = EVEN_DIGIT; break; } |
但是,如果你有一个case标签,后面跟着代码,然后又落到了另一个case标签上,那么我几乎总是认为那是邪恶的。也许将公共代码移动到一个函数,并从两个地方调用是一个更好的主意。
请注意,我用C++的FAQ定义"邪恶"
这是一把双刃剑。有时很有用,有时很危险。
什么时候好?当你想要10个案件都以同样的方式处理时…
1 2 3 4 5 6 7 8 9 10 11 12 13 | switch (c) { case 1: case 2: ... do some of the work ... /* FALLTHROUGH */ case 17: ... do something ... break; case 5: case 43: ... do something else ... break; } |
我喜欢的一个规则是,如果你在排除休息的地方做任何幻想,你需要一个明确的评论/*Fallthrough*/来表明这是你的意图。
根据你在做什么,通过失败确实是一件很方便的事情。考虑一下这种简洁易懂的方式来安排选项:
1 2 3 4 5 6 7 8 9 10 11 | switch ($someoption) { case 'a': case 'b': case 'c': // do something break; case 'd': case 'e': // do something else break; } |
想象一下用if/else这样做。会很混乱的。
你听说过达夫的装置吗?这是使用switch fallthrough的一个很好的例子。
它是一个可以使用和滥用的特性,就像几乎所有的语言特性一样。
它可以非常有用的几次,但一般来说,没有失败是所期望的行为。应允许Fallthrough,但不能隐式。
例如,要更新某些数据的旧版本:
1 2 3 4 5 6 7 8 9 10 | switch (version) { case 1: // update some stuff case 2: // update more stuff case 3: // update even more stuff case 4: // and so on } |
我希望在开关中使用不同的回退语法,比如,errr.。
1 2 3 4 5 6 7 8 9 | switch(myParam) { case 0 or 1 or 2: // do something; break; case 3 or 4: // do something else; break; } |
注意:如果使用标志声明枚举中的所有情况,那么使用枚举就已经可以了,对吗?听起来也没那么糟糕,案件可以(应该?)很好,已经成为你枚举的一部分了。
也许这对于使用扩展方法的流畅接口来说是个不错的例子(没有双关语的意思)?比如,呃…
1 2 3 4 5 | int value = 10; value.Switch() .Case(() => { /* do something; */ }, new {0, 1, 2}) .Case(() => { /* do something else */ } new {3, 4}) .Default(() => { /* do the default case; */ }); |
尽管这可能更难理解:p
与任何东西一样:如果小心使用,它可以是一个优雅的工具。
然而,我认为缺点不仅仅是证明不使用它是正当的,而且最终不再允许使用它(c)。问题包括:
- 很容易"忘记"休息
- 对于代码维护人员来说,省略的中断并不总是有意的。
正确使用开关/案例贯穿:
1 2 3 4 5 6 7 8 | switch (x) { case 1: case 2: case 3: do something break; } |
Baaaad使用开关/案例Fallthrough:
1 2 3 4 5 6 7 8 9 10 | switch (x) { case 1: some code case 2: some more code case 3: even more code break; } |
在我看来,这可以使用if/else构造重写,而不会造成任何损失。
我的最后一句话:不要像坏例子中那样掉入大小写标签,除非您在使用和理解这种样式的地方维护遗留代码。
强大而危险。失败的最大问题是它不明确。例如,如果您遇到频繁编辑的代码,这些代码有一个切换,并且有故障,那么您如何知道这是有意的而不是错误?
在我使用它的任何地方,我都确保它被正确地评论:
1 2 3 4 5 6 7 | switch($var) { case 'first': // fall-through case 'second': i++; break; } |
像在第一个例子中那样使用fall-through显然是可以的,我不会认为它是真正的fall-through。
第二个例子是危险的(如果没有广泛的评论)不明显的。我教我的学生不要使用这样的结构,除非他们认为值得对它投入一个注释块,这说明这是一个有意的错误,以及为什么这个解决方案比其他方案更好。这不鼓励草率的使用,但在使用它有好处的情况下仍然允许使用它。
这或多或少相当于当有人想违反编码标准时我们在空间项目中所做的:他们必须申请豁免(我被要求就裁决提出建议)。
我不喜欢我的
如果一个switch语句的多个分支想要使用一些公共代码,那么我将其提取到一个单独的公共函数中,该函数可以在任何分支中调用。
只有当fall think用作代码块的跳转表时,才应使用它。如果在更多的案例之前代码中有任何部分有无条件中断,那么所有案例组都应该以这种方式结束。任何其他东西都是"邪恶的"。
在某些情况下,使用fall through是程序员的一种懒惰行为——例如,他们可以使用一系列语句,但可以使用一系列"catch all"开关案例。
尽管如此,当我知道最终我还是需要这些选项(例如在菜单响应中),但还没有实现所有的选择时,我发现它们特别有用。同样地,如果您同时为"a"和"a"执行一个fall through,我发现使用switch fall through比使用复合if语句要干净得多。
这可能是风格的问题和程序员的想法,但我一般不喜欢以"安全"的名义删除语言的组成部分,这就是为什么我倾向于C和它的变体/后代,而不是说Java。我喜欢能用指针之类的东西到处闲逛,即使我没有理由这样做。