在不能处理指定的情况下,在switch语句中抛出exception

假设我们有一个函数在MVC应用程序中为系统中的用户更改密码。

public JsonResult ChangePassword (string username, string currentPassword, string newPassword) { switch (this.membershipService.ValidateLogin(username, currentPassword)) { case UserValidationResult.BasUsername: case UserValidationResult.BadPassword: // abort: return JsonResult with localized error message // for invalid username/pass combo. case UserValidationResult.TrialExpired // abort: return JsonResult with localized error message // that user cannot login because their trial period has expired case UserValidationResult.Success: break; } // NOW change password now that user is validated } 

membershipService.ValidateLogin()返回一个UserValidationResult枚举,定义如下:

 enum UserValidationResult { BadUsername, BadPassword, TrialExpired, Success } 

作为一个防御性的程序员,如果从ValidateLogin()返回一个无法识别的UserValidationResult值,我会改变上面的ChangePassword()方法来抛出exception:

 public JsonResult ChangePassword (string username, string currentPassword, string newPassword) { switch (this.membershipService.ValidateLogin(username, currentPassword)) { case UserValidationResult.BasUsername: case UserValidationResult.BadPassword: // abort: return JsonResult with localized error message // for invalid username/pass combo. case UserValidationResult.TrialExpired // abort: return JsonResult with localized error message // that user cannot login because their trial period has expired case UserValidationResult.Success: break; default: throw new NotImplementedException ("Unrecognized UserValidationResult value."); // or NotSupportedException() break; } // Change password now that user is validated } 

我总是认为最好的做法就像最后一个片段一样。 例如,如果一个开发人员现在要求用户尝试login,那么如果出于这种或那种商业原因,他们应该首先联系业务? 所以UserValidationResult的定义更新为:

 enum UserValidationResult { BadUsername, BadPassword, TrialExpired, ContactUs, Success } 

开发人员更改ValidateLogin()方法的主体,以在适用时返回新的枚举值( UserValidationResult.ContactUs ),但忘记更新ChangePassword() 。 在交换机中没有例外的情况下,用户仍然可以在login尝试不被validation的时候更改密码!

是只是我,还是这个default: throw new Exception()一个好主意? 我几年前就看到了它,并且总是(在琢磨之后)认为这是最好的做法。

在这种情况下,我总是抛出exception。 考虑使用InvalidEnumArgumentException ,在这种情况下可以提供更丰富的信息。

使用你所拥有的,虽然break语句之后它将永远不会被命中,因为在抛出和未处理exception时,该线程的执行将会停止。

我曾经使用过这种做法,当枚举距离它的使用“远”时,但是在枚举非常接近并且专用于特定特性的情况下,它似乎有点多。

很可能,我怀疑debugging断言失败可能更合适。

http://msdn.microsoft.com/en-us/library/6z60kt1f.aspx

请注意第二个代码示例…

我总是喜欢做你所拥有的,虽然我通常抛出一个ArgumentException如果它是一个参数传入,但我有点像NotImplementedException更好,因为错误可能是一个新的case语句应该被添加,而不是调用者应该将参数更改为支持的参数。

在switch语句中包含的throw语句后,我从不添加中断。 不用担心的是恼人的“无法检测到的代码”警告。 所以是的,这是一个好主意。

我认为这样的方法可能非常有用(例如,请参阅错误的空代码path )。 如果你可以在释放的代码中编译默认值,比如assert,那更好。 这样你在开发和testing时就有了额外的防御性,但是在发布的时候不会产生额外的代码开销。

你说的是你很防守,我可能会说这太过分了。 其他开发人员是不是要testing他们的代码? 当然,当他们做最简单的testing时,他们会看到用户仍然可以login – 那么他们就会意识到他们需要修复的东西。 你所做的不是可怕的或错误的,但是如果你花了很多时间去做,那可能太多了。

对于一个Web应用程序,我更喜欢一个默认的产生结果的错误消息,要求用户联系pipe理员并logging一个错误,而不是引发一个exception,这可能会导致用户的意义不太重要。 因为我可以预料,在这种情况下,回报价值可能不是我所期望的,我不认为这是真正的特殊行为。

此外,你的用例,导致额外的枚举值不应该引入一个错误到你的特定方法。 我的期望是,用例只适用于login – 这将发生在ChangePassword方法可以合法地被调用之前,可能,因为你想确保该人在更改密码之前login – 你应该从来没有实际看到作为密码validation返回的ContactUs值。 用例将指定如果联系结果返回,则authentication失败,直到结果条件被清除。 如果不是这样的话,我希望你会对系统的其他部分应该如何在这种情况下作出反应有所要求,并且你会写一些testing,迫使你改变这个方法中的代码以符合这些testing。解决新的返回值。

validation运行时约束通常是一件好事,所以最好的做法是帮助实现“快速失败”原则,并且在检测到错误情况时停止(或logging),而不是默默继续。 有些情况是不正确的,但是如果给出switch语句,我想我们不会经常看到它们。

详细来说,switch语句总是可以被if / else块replace。 从这个angular度来看,我们看到抛出vs不要抛出一个默认开关的情况大致相当于这个例子:

  if( i == 0 ) { } else { // i > 0 } 

VS

  if( i == 0 ) { } else if ( i > 0 ) { } else { // i < 0 is now an enforced error throw new Illegal(...) } 

第二个例子通常被认为是更好的,因为当错误约束被违反时你失败了,而不是在潜在的不正确假设下继续。

如果我们想要:

  if( i == 0 ) { } else { // i != 0 // we can handle both positve or negative, just not zero } 

那么,我怀疑在实践中,最后一种情况可能会出现在if / else语句中。 因为switch语句经常类似于第二个if / else块,所以通常这是最佳做法。

还有一些考虑是值得的: – 考虑多态方法或枚举方法来完全替代switch语句,例如: C#中枚举的方法 – 如果抛出是最好的,正如其他答案中指出的那样,更喜欢使用特定的exceptiontypes避免锅炉板代码,例如: InvalidEnumArgumentException