假设我们有一个函数可以更改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
}
作为一名防御性程序员,如果从ChangePassword()
返回无法识别的UserValidationResult
值,我会更改上述{{1}}方法以抛出异常:
ValidateLogin()
我一直认为模式就像最佳实践之上的最后一个片段。例如,如果一个开发人员获得了一个要求,即当用户尝试登录时,如果出于这个或那个业务原因,他们会假设首先联系该业务,该怎么办?所以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
}
的定义更新为:
UserValidationResult
开发人员更改enum UserValidationResult
{
BadUsername,
BadPassword,
TrialExpired,
ContactUs,
Success
}
方法的正文以在适用时返回新的枚举值(ValidateLogin()
),但忘记更新UserValidationResult.ContactUs
。如果没有交换机中的例外,用户仍然可以在首次验证登录尝试时更改密码!
只是我,还是这个ChangePassword()
好主意?我几年前就看到了它,并且总是(在研究之后)认为这是一种最佳实践。
答案 0 :(得分:79)
在这种情况下,我总是抛出异常。考虑使用InvalidEnumArgumentException
,在这种情况下可以提供更丰富的信息。
答案 1 :(得分:3)
使用你所拥有的它很好,虽然它之后的break语句永远不会被命中,因为当抛出异常并且未处理时,该线程的执行将停止。
答案 2 :(得分:3)
我之前使用过这种做法的特定实例,当枚举与它的使用“相距甚远”时,但是在枚举非常接近且专用于特定功能的情况下,它似乎有点多了。
很可能,我怀疑调试断言失败可能更合适。
http://msdn.microsoft.com/en-us/library/6z60kt1f.aspx
请注意第二个代码示例......
答案 3 :(得分:2)
我总是喜欢做你所拥有的,虽然我通常会抛出ArgumentException
如果它是传入的参数,但我更喜欢NotImplementedException
,因为错误很可能是应该添加新的case语句,而不是调用者应该将参数更改为受支持的语句。
答案 4 :(得分:0)
我从不在switch语句中包含throw语句之后添加一个break。最令人担忧的是烦人的“无法检测到的代码”警告。所以是的,这是一个好主意。
答案 5 :(得分:0)
我认为这样的方法非常有用(例如,见Erroneously empty code paths)。如果你可以在释放的代码中编译默认抛出,就像断言一样,那就更好了。这样,您在开发和测试期间就具有额外的防御性,但在发布时不会产生额外的代码开销。
答案 6 :(得分:0)
你确实说你很防守,我几乎可以说这太过分了。是不是其他开发人员要测试他们的代码?当然,当他们进行最简单的测试时,他们会看到用户仍然可以登录 - 因此,他们将意识到他们需要修复的内容。你所做的并不是可怕或错误,但如果你花了很多时间做这件事,那可能就太多了。
答案 7 :(得分:0)
对于Web应用程序,我更喜欢使用错误消息生成结果的默认值,该错误消息要求用户联系管理员并记录错误,而不是抛出可能导致用户意义不大的异常。因为在这种情况下,我可以预期返回值可能不是我预期的,我不会认为这是一种真正特殊的行为。
此外,导致额外枚举值的用例不应该在您的特定方法中引入错误。我的期望是用例仅适用于登录 - 这可能发生在ChangePassword方法可以合法调用之前,大概是因为你想确保该人在更改密码之前已登录 - 你应该从来没有真正看到ContactUs值作为密码验证的回报。用例将指定如果返回ContactUs结果,则该身份验证将失败,直到导致结果的条件被清除。如果是这样的话,我希望你对系统的其他部分在这种情况下应该如何反应有所要求,并且你会编写测试,迫使你改变这种方法中的代码以符合那些测试和解决新的返回值。
答案 8 :(得分:0)
验证运行时约束通常是件好事,所以是的,最好的做法,有助于快速失败'原则,你在检测错误条件时停止(或记录),而不是静默地继续。有些情况并非如此,但考虑到转换语句,我怀疑我们不会经常看到它们。
详细说明,switch语句总是可以用if / else块替换。从这个角度来看,我们看到throw vs不抛出一个默认的switch情况大约相当于这个例子:
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语句,例如:Methods inside enum in C#
- 如果投掷是最好的,如其他答案所述,更喜欢使用特定的例外类型来避免锅炉板代码,例如:InvalidEnumArgumentException