使用切换案例时是否有必要添加默认案例?


41

在最近的代码审查中,我被要求default在所有switch使用块的文件中放入大小写,即使在中没有任何关系default。这意味着我必须摆放default箱子,什么也不要写。

这是正确的做法吗?它有什么作用?


9
由您决定...我的意思是主管的代码风格。
SD

1
我认为甚至在某些情况下,您也不需要默认情况。考虑切换一个枚举。您的开关为所有可能的值定义了大小写。现在将值添加到枚举导致在该开关中添加大小写。如果您不这样做,则可能是一个错误,因为您对此新的枚举值进行操作,而您的代码则不需要。您的编译器可以警告您(我认为;不确定Java),但前提是您设置默认大小写。另外,如果您确定代码永远不会到达警告,则可以在默认情况下打印警告。
leemes 2013年

6
我和我的朋友将此称为“开发人员异常”情况-当代码中发生某些您希望发生的事情(由于逻辑/代码原因),然后我们添加了一个“开发人员异常”。这样,如果确实发生了这种情况,则会引发“开发人员异常”,并且至少我们知道问题出在哪里了。
Marco

Answers:


31

似乎在三种情况下default不需要声明:

  1. 不会留下其他情况,因为输入的值集有限switch case。但这可能会随时间而改变(有意或无意),default case如果有任何改变,最好_您可以记录或警告用户有关错误的值。

  2. 您知道switch case将在何处使用以及将使用什么值。同样,这可能会改变,可能需要额外的处理。

  3. 其他情况不需要任何特殊处理。如果是这种情况,我认为您需要添加default case,因为它是一种公认​​的编码样式,它使您的代码更具可读性。

前两种情况是基于假设的。因此(假设您由于没有定期进行代码审查而在一个不太小的团队中工作),则您无法承担这些假设。您不知道谁将使用您的代码或在代码中调用函数/调用方法。同样,您可能需要使用其他人的代码。具有相同的编码样式将使处理某人(包括您)的代码更加容易。


14
在情况1和2中,默认情况应为错误。如果您的编码风格始终使用default,则可以这样做,但要使其在1和2中发出错误。可能的是,它应该在编译时发出它,但是,如果在Java中,则并非总是可能的。我至少认为,对于用户而言,获得重要信息是很重要的:“您正在通过传递此值来射击自己的脚”
martiert

11
默认情况总是一个好主意,因为例如在枚举的情况下,如果有人向该枚举添加了新条目,则您的默认情况应执行与向throw new IllegalStateException("Unrecognised case "+myEnum)您显示错误的位置和错误类似的操作。
2013年

这是否意味着即使您知道不应该出现此else子句,您也应该始终在每个子句之后都有一个子句if/else if?对我来说似乎不是个好主意……
jadkik94 2013年

2
如果我可以在工作中添加一个轶事:我有一个工厂方法,该方法使用枚举实例化某些类。我留下的指示是,每当您向该项目添加新类时,都必须向枚举和开关的大小写添加值。当然,在关闭代码一周后,它便停止工作,但永远不能引发异常。我去找程序员,问他:“您在交换机上加了个保护套吗?” 当然可以,他没有。那天,我更改了例外,说“如果您收到此消息,请怪罪于最后一位接触代码的开发人员”。
2013年

28

这是正确的操作吗?它有什么作用?

公司编码标准要求所有switch语句都使用默认大小写并不少见。原因之一是它使读者可以轻松找到末尾switch。另一个可能更好的原因是,它使您思考条件不符合您的期望时代码应该做什么。不管有何要求,如果是公司标准,除非有气密的理由,否则都应遵循。

如果您认为switch每种情况都包含案例,那么最好assert在默认案例中添加一条语句。这样,当某人更改了代码并无意中添加了您switch无法涵盖的条件时,他们会打到assert并意识到他们需要解决部分代码。

如果您switch仅涵盖了一些可能的条件,但对其他情况则无需做任何特殊处理,则可以将默认情况保留为空。在这种情况下添加注释以指示默认情况是有意为空是一个好主意,因为达到该条件的条件不需要完成任何工作。


1
什么是assert
FLY 2013年

1
@FLY它是Java中的关键字,通常是C,C ++等中的宏。无论哪种方式,都使用assert来测试某些假设是否成立,并抛出异常,否则将引发错误。
Caleb 2013年

我已经编辑我以前的评论并链接到维基百科
FLY

您不应该留空案子。如果您的开关未涵盖所有可能的条件,则应出于建议在第二段中在默认情况下添加断言的相同原因,在默认情况下断言其他条件。
rold2007

12

如果“切换”为纯枚举类型,则具有默认回退是很危险的。以后将值添加到枚举类型时,编译器将突出显示未包含新值的开关。如果那里有default子句,编译器将保持沉默,您可能会错过它。


1
+1是在编译时而不是在运行时捕获问题。
SylvainD

我不知道投票和接受的答案有多么完全误导。快速搜索表明Java编译器有这种警告。伙计们,您到底为什么要在运行时等待错误?
Artur 2013年

我不知道这种行为。我的ideone代码示例ideone.com/WvytWq提出了其他建议。假设您说的是真的,那么如果有人将值添加到枚举类型中,但是您不重新编译代码会怎样?
2013年


1
显然,没有默认情况下,新值不会由switch语句处理。但是,如果在接口更改时不重新编译实现,则构建系统确实会损坏。
Artur 2013年

9

在许多方面,该问题与经常问到的我是否需要elseif/ else ifladder 的末尾包含一个覆盖每个选项的子句相同。

从句法上来说,答案是,不,你不会。但是有一个...

default子句可以在那里为(至少)两个原因:

  1. 作为错误处理程序-当然,永远不要到这里!是可以提出的要求,但是如果可以,该怎么办? 数据损坏,甚至更糟的是,如果没有正确捕获,则没有数据验证是导致程序失败的途径。在这种情况下,它不应为空子句!
  2. 设计/代码覆盖率-即使以最简单的流程图形式,从if语句也有两条路线,而且总是otherwise从案例出发。没有任何理由不将这些内容包括在源代码中。

我的想法总是很简单-评估这两种方案的最坏情况,并寻求最安全的方案。在为空elsedefault子句的情况下,最坏的情况是:

  • 包括它们:额外的三或四行“冗余”代码。
  • 不包括它们:未捕获流氓数据或意外情况,有可能导致程序失败。

太过分了吗?也许...但是,如果我的软件出了错,那我就有杀人的潜力。我不想冒险。


顺便说一句,MISRA-C准则 {请参见从属关系}建议default为每个switch


不仅在阶梯的尽头。问题是:我需要一个else之后if。但是正如您所说,不,不是。
ott--

作为错误处理程序,我建议稍加修改的表单应避免使用空默认值。至于杀人,我认为这实际上并不重要-不管您是否拥有默认条款,人民都会死了,但是这将使人们更容易弄清他们为什么死。
2013年

好点,@ emory-不太清楚,是...编辑:)
Andrew

6

Java不强迫你有一个“默认”的语句,但它是很好的做法有一个所有的时间,即使代码可能永远无法达到(现在)。原因如下:

通过使用无法访问的默认子句,可以向代码阅读器显示您考虑的值并知道您在做什么。您还允许将来进行更改,例如:添加了一个新的枚举值,开关不应默默地忽略该新值;否则,您将无法更改。您可以在该处引发Exception或执行其他操作。

要捕获传入的意外值(以防万一您没有打开枚举),例如,它可能大于或小于预期。

处理“默认”操作-这些开关用于特殊行为。例如,变量可能在开关外部声明,但未初始化,并且每种情况都会将其初始化为其他变量。在这种情况下,默认值可能会将其初始化为默认值,因此切换后立即执行的代码不会出错/不会引发异常。

即使您决定不将任何内容设置为默认值(没有异常,日志记录等),也可以使用一条评论说您认为默认值将永远不会发生,这可能有助于提高代码的可读性。但这取决于个人喜好。


2

我还要补充一点,这取决于您使用的语言的哲学。例如,在Erlang中,“崩溃”是一种常见的处理方法,您不会定义默认情况,但是case如果发生了无法解决的情况,则让语句引发错误。


0

除非您已证明并永久涵盖所有情况,否则您应该始终具有默认值。它不花钱,而且可以保证在不断发展的程序中无法维护您的switch语句。

如果您真的确定自己不关心其他任何情况,则默认值:break; //无关,但默认值应类似于默认值:throw new Error(“ unexpected value”);

同样,在未添加打算删除的效果的注释之前,切勿“删除”非平凡的案例构造。Java在设计阶段就弄错了这一点。


-2

考虑

enum Gender
{
       MALE ,
       FEMALE ;
}

void run ( Gender g )
{
      switch ( g )
      {
           case MALE :
                 // code related to males
                 break ;
           default :
                 assert Gender . FEMALE . equals ( g ) ;
                 // code related to females
                 break ;
      }
}

我认为这比具有MALE情况,FEMALE情况和引发异常的默认情况更好。

在测试阶段,计算机将检查g是否为FEMALE。在生产过程中,将省略检查。(是的,微优化!)

更重要的是,您将保持100%代码覆盖率的目标。如果您编写了一个引发异常的默认案例,那么您将如何对其进行测试?


1
1.无需进行微优化-过去30年左右的编译器中使用了消除死代码的方法,并且JIT会消除它。2.通常不认为100%的代码覆盖率很重要(一方面,100%的覆盖率可能无法捕获所有边缘情况,另一方面,没有测试会捕获正确程序中的assert_not_reached行)。在这种情况下,不保留任何默认情况将导致编译器错误。另一方面,您将如何检查断言是否起作用(您将问题转移并以100%的覆盖率将其隐藏,而不是使用同一行“我保证,不会发生”)。
Maciej Piechotka

1
3.什么时候Gender . FEMALE . equals ( FEMALE ) ;评估false?您的意思是,Gender.FEMALE.equals (g)但是由于可以依赖对稳定枚举的引用,因此可以编写g == Gender.FEMALE。4.(个人意见)这样的代码更难阅读,并且会产生稍微混乱的错误。
Maciej Piechotka

@MaciejPiechotka很好地抓住了g。是的,微优化是无益的,但也不是缺点。如果100%的代码覆盖率是您的目标之一,并且您的代码覆盖率工具不检查断言(不应该这样做),则这是一个好处。
2013年

那为什么会成为我的目标之一?应该针对所有边缘情况(无论它们是否映射到代码行)测试“有趣的”部分,并且可以简单地跳过“无聊的”部分以节省测试时间。恕我直言,代码覆盖率是测试的错误指标。
Maciej Piechotka 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.