views:

129

answers:

8

I've been writing C# for the last 8 years and have become quite the defensive OOP programmer. Working in a statically-typed language, you do things like validate arguments in methods and throw exceptions where you wouldn't in languages that are dynamic and more liberal. I consider myself an expert in C# and am looking for feedback from other well-versed C# developers on best practices.

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.

+3  A: 

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
But will it *compile* without the break?
CantSleepAgain
I'm pretty sure it will `compile`, just like it'll compile if you have a return statement without a `break`
Davy8
Based on ChaosPandion's similar answer, you must be right on this. Thanks. That's what I get for writing code in Notepad :)
CantSleepAgain
it won't compile, it should say unreachable statement/code on that break statement
Jesus Ramos
unreachable code is a warning, not an error. Perhaps you have warnings as errors turned on?
Matt Greer
A: 

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
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
A: 

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
A: 

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
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
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
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
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
+1  A: 

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
A: 

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
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
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
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
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
+12  A: 

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

Matt Greer
Oooh very nice, hadn't seen this Exception derivative before. Thanks :)
CantSleepAgain
Interesting, I wish it was in one of the namespaces that are included by default when creating a new class in visual studio.
Davy8
A: 

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
"My expectation would be that the use case only applies on login -- which would happen <b>before</b> 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 <i>specific</i> 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