当没有指定的案例可以处理时,在switch语句中抛出异常

时间:2010-07-28 02:47:51

标签: c# exception switch-statement

假设我们有一个函数可以更改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()好主意?我几年前就看到了它,并且总是(在研究之后)认为这是一种最佳实践。

9 个答案:

答案 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