80

Let's say we have a function that changes a password for a user in a system in an MVC app.:

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() returns a UserValidationResult enum that is defined as:

enum UserValidationResult
{
    BadUsername,
    BadPassword,
    TrialExpired,
    Success
}

Being a defensive programmer, I would change the above ChangePassword() method to throw an exception if there's an unrecognized UserValidationResult value back from 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
}

I always considered a pattern like the last snippet above a best practice. For example, what if one developer gets a requirement that now when users try to login, if for this-or-that business reason, they're suppose to contact the business first? So UserValidationResult's definition is updated to be:

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

The developer changes the body of the ValidateLogin() method to return the new enum value (UserValidationResult.ContactUs) when applicable, but forgets to update ChangePassword(). Without the exception in the switch, the user is still allowed to change their password when their login attempt shouldn't even be validated in the first place!

Is it just me, or is this default: throw new Exception() a good idea? I saw it some years ago and always (after groking it) assume it to be a best practice.

Nisarg Shah
  • 14,151
  • 6
  • 34
  • 55
CantSleepAgain
  • 3,743
  • 3
  • 21
  • 18

9 Answers9

120

I always throw an exception in this case. Consider using InvalidEnumArgumentException, which gives richer information in this situation.

Matt Greer
  • 60,826
  • 17
  • 123
  • 123
  • 3
    Oooh very nice, hadn't seen this Exception derivative before. Thanks :) – CantSleepAgain Jul 28 '10 at 03:04
  • Interesting, I wish it was in one of the namespaces that are included by default when creating a new class in visual studio. – Davy8 Jul 28 '10 at 03:10
  • 4
    This is semantically correct only when the subjected enum is an argument. That's not always the case: http://stackoverflow.com/questions/13645149/what-is-the-correct-exception-to-throw-for-unhandled-enum-values – Sedat Kapanoglu Aug 21 '13 at 09:01
  • @SedatKapanoglu You always can put switch into separate method where enum will be argument. – Stas Boyarincev Mar 29 '18 at 10:16
  • 1
    @StasBoyarincev yes, that's possible. too much work though. – Sedat Kapanoglu Mar 29 '18 at 23:17
  • 12
    I think the answer would be even better if it showed a code snippet with the usage. Something like `default: throw new System.ComponentModel.InvalidEnumArgumentException(nameof(userType), (int)userType, userType.GetType());` – styfle Apr 06 '18 at 19:57
  • I suggest ArgumentException() for non-Enum parameters like strings. – Guney Ozsan Sep 30 '21 at 12:42
3

With what you have it is fine although the break statement after it will never be hit because execution of that thread will cease when an exception is thrown and unhandled.

Jesus Ramos
  • 22,940
  • 10
  • 58
  • 88
3

I always like to do exactly what you have, although I usually throw an ArgumentException if it's an argument that was passed in, but I kind of like NotImplementedException better since the error is likely that a new case statement should be added rather than the caller should change the argument to a supported one.

Davy8
  • 30,868
  • 25
  • 115
  • 173
3

I've used this practice before for specific instances when the enumeration lives "far" from it's use, but in cases where the enumeration is really close and dedicated to specific feature it seems like a little bit much.

In all likelihood, I suspect an debug assertion failure is probably more appropriate.

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

Note the second code sample...

Rob Cooke
  • 968
  • 1
  • 6
  • 12
  • 2
    How is a "debug assertion failure" different than an exception being throw? Just that the code responsible for reporting the failure doesn't make it into release code? Why assert failure when you can throw exceptions--isn't that the point of exceptions? – CantSleepAgain Jul 28 '10 at 03:04
  • 1
    If the option being ignored by the switch is the desired behavior, a debug assertion failure will not cause the code to fail if built in Release mode. – Rob Cooke Jul 28 '10 at 03:07
  • 2
    So you're saying you'd rather see a System.Diagonstics.Debug.Assert() than an exception being thrown? Just trying to follow you. The problem with asserting failure in #debug is that in release, ChangePassword() still changes the password. With an Exception, they'd get a HTTP 500. Which is better is an excellent and difficult answer. – CantSleepAgain Jul 28 '10 at 03:15
  • I think for your specific example where falling through would cause undesirable results, throwing an exception (or returning) is appropriate, but I think in the general case using Debug.Fail would be a good alternative -depending on how your code is tested and deployed. – Rob Cooke Jul 28 '10 at 03:22
0

I think approaches like this can be very useful (for example, see Erroneously empty code paths). It's even better if you can have that default-throw be compiled away in released code, like an assert. That way you have the extra defensiveness during development and testing, but don't incur the extra code overhead when releasing.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • I don't think it's a good idea to ignore the exception. The overhead you mentioned is definitely negligible and letting your code ignore unhandled value could have worse side effects. For instance it could be the only case where you shouldn't delete the file afterwards. If you ignore it, you lose the data. – Sedat Kapanoglu Aug 21 '13 at 09:04
0

You do state that you're being very defensive and I might almost say this is overboard. Isn't the other developer going to test their code? Surely, when they do the simplest of tests they'll see that the user can still log in - so, then they'll realize what they need to fix. What you're doing isn't horrible or wrong, but if you're spending a lot of your time doing it, it might just be too much.

Chad
  • 3,159
  • 4
  • 33
  • 43
  • 4
    I disagree. The caller of the code *should* test all the code paths, but *should* and *will* are two different things. Even if it is tested, if you're in an environment that doesn't do unit testing, it might take more time to track down exactly what caused the issue. – Davy8 Jul 28 '10 at 03:02
  • 1
    Actually this wouldn't necessarily be caught by a unit test because if the tests were written before the new enum value existed then it most likely would not have been tested for, and testing of the calling method may have this class as a mock object, so it'd only be caught in integration testing, in which case an exception thrown would alert of an error that could possibly be missed (with rushed deadlines things aren't always tested thoroughly) and give the exact location of the error. – Davy8 Jul 28 '10 at 03:07
  • You also can't be certain that every person working on a project knows(or at least remembers at the time) every place something is implemented. Throwing an exception is hardly going overboard. – rossisdead Jul 28 '10 at 03:29
  • It's not that I don't think it's a bad idea, it's that the OP was asking as if they weren't sure if it was the best way. I suppose - conceding to all of your points - that it is a good idea. However, it shouldn't be the end all. I was trying to say there are other things the OP can and should do to prevent future mishaps. New developers *should* be testing their new features or a tester *should* be. What happens when the OP forgets to add one of these default statements, but a new developer is relying on the OP having added it? Again, there's more here than simply throwing an exception. – Chad Jul 28 '10 at 06:28
0

For a web application I would prefer a default that generates a result with an error message that asks the user to contact an adminstrator and logs an error rather than throws an exception that might result in something less meaningful to the user. Since I can anticipate, in this case, that the return value might be something other than what I expect I would not consider this truly exceptional behavior.

Also, your use case that results in the additional enum value shouldn't introduce an error into your particular method. My expectation would be that the use case only applies on login -- which would happen before the ChangePassword method could legally be called, presumably, since you'd want to make sure that the person was logged in before changing their password -- you should never actually see the ContactUs value as a return from the password validation. The use case would specify that if a ContactUs result is return, that authentication fails until the condition resulting in the result is cleared. If it were otherwise, I'd expect that you'd have requirements for how other parts of the system should react under that condition and you'd write tests that would force you to change the code in this method to conform to those tests and address the new return value.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • "My expectation would be that the use case only applies on login -- which would happen before the ChangePassword method could legally be called, presumably, since you'd want to make sure that the person was logged in before changing their password -- you should never actually see the ContactUs value as a return from the password validation." I was trying to think of a good example, but you're right, this specific scenario shouldn't happen if everything is architected correctly. I should have put more thought into a better example but I think the point still stands, no? – CantSleepAgain Aug 04 '10 at 03:21
0

Validating runtime constraints is usually a good thing, so yes, best practice, helps with 'fail fast' principle and you are halting (or logging) when detecting an error condition rather than continuing silently. There are cases where that is not true, but given switch statements I suspect we do not see them very often.

To elaborate, switch statements can always be replaced by if/else blocks. Looking at it from this perspective, we see the throw vs do not throw in a default switch case is about equivalent to this example:

    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(...)
    }

The second example is usually considered better since you get a failure when an error constraint is violated rather than continuing under a potentially incorrect assumption.

If instead we want:

    if( i == 0 ) {


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

Then, I suspect in practice that last case will likely appear as an if/else statement. Because of that the switch statement so often resembles the second if/else block, that the throws is usually a best practice.

A few more considerations are worthwhile: - consider a polymorphic approach or an enum method to replace the switch statement altogether, eg: Methods inside enum in C# - if throwing is the best, as noted in other answers prefer to use a specific exception type to avoid boiler plate code, eg: InvalidEnumArgumentException

LaFayette
  • 363
  • 2
  • 7
-1

I never add a break after a throw statement contained in a switch statement. Not the least of the concerns is the annoying "unreachable code detected" warning. So yes, it is a good idea.

ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
  • Sounds like Jesus Ramos was right (and you of course). Thanks--I did a couple iterations of this and overlooked the unnecessary break after the throw. Kudos :) – CantSleepAgain Jul 28 '10 at 03:08