views:

484

answers:

12

Suppose we have a method that accepts a value of an enumeration. After this method checks that the value is valid, it switches over the possible values. So the question is, what is the preferred method of handling unexpected values after the value range has been validated?

For example:

enum Mood { Happy, Sad }

public void PrintMood(Mood mood)
{
    if (!Enum.IsDefined(typeof(Mood), mood))
    {
        throw new ArgumentOutOfRangeException("mood");
    }

    switch (mood)
    {
        case Happy: Console.WriteLine("I am happy"); break;
        case Sad:   Console.WriteLine("I am sad"); break;
        default: // what should we do here?
    }

What is the preferred method of handling the default case?

  • Leave a comment like // can never happen
  • Debug.Fail() (or Debug.Assert(false))
  • throw new NotImplementedException() (or any other exception)
  • Some other way I haven't thought of
+4  A: 

I prefer to throw new NotImplementedException("Unhandled Mood: " + mood). The point is that the enumeration may change in the future, and this method may not be updated accordingly. Throwing an exception seems to be the safest method.

I don't like the Debug.Fail() method, because the method may be part of a library, and the new values might not be tested in debug mode. Other applications using that library can face weird runtime behaviour in that case, while in the case of throwing an exception the error will be known immediately.

Hosam Aly
This should have been edited into your original post.
SnOrfus
@SnOrfus: Why? It's a perfectly valid answer.
Michael Myers
As I said in my answer, it may be valid, but it's wrong being terrible OO...
Bill K
@SnOrfus: Writing it in the original post wouldn't allow people to vote on it. By writing it as a separate answer, the community has the ability to vote the answer independently of the question, and at the end the best answer will get the most votes (regardless of who answered it).
Hosam Aly
I am choosing this as the accepted answer, because currently it is the highest voted one.
Hosam Aly
+1  A: 

You could have a trace for the default calling out the value of the passed enum. Throwing exceptions is OK but in a large application there will be several places where your code does not care about other values of the enum.
So, unless you are sure that the code intends to handle all possible values of the enum, you'll have to go back later and remove the exception.

Mohit Chakraborty
+3  A: 

In Java, the standard way is to throw an AssertionError, for two reasons:

  1. This ensures that even if asserts are disabled, an error is thrown.
  2. You're asserting that there are no other enum values, so AssertionError documents your assumptions better than NotImplementedException (which Java doesn't have anyway).
Michael Myers
But AssertionError extends java.lang.Error, not java.lang.Exception. This would mean that other layers in the code will not have a chance to catch the exception and recover from it. (Technically speaking they can, but practically most people don't (and shouldn't) catch java.lang.Error.)
Hosam Aly
If you already checked whether it was valid, the AssertionError should never happen (that is, after all, what you are asserting). If it does happen, things are so wrong that I think it would be pointless to try to catch it. But I do see that sometimes it might be useful to make it catchable.
Michael Myers
See also the discussion in this question: http://stackoverflow.com/questions/398953/java-what-is-the-preferred-throwable-to-use-in-a-private-utility-class-construct
Michael Myers
But "valid" does not necessarily mean "handled". It's a valid value for the enum (well, in Java this is guaranteed at compile time, so we don't need to check), but it may be a new addition that was not there when the method was developed. I don't like throwing an Error in this case.
Hosam Aly
In that case, you need a compile-time warning. The comments on this answer: http://stackoverflow.com/questions/577943/how-accurate-are-the-technical-arguments-in-jwzs-ten-year-old-java-sucks-article/589689#589689 indicate that Eclipse can give such a warning, but unfortunately it's not standard.
Michael Myers
+2  A: 

For pretty much every switch statement in my code base, I have the following default case

switch( value ) { 
...
default:
  Contract.InvalidEnumValue(value);
}

The method will throw an exception detailing the value of the enum at the point an error was detected.

public static void InvalidEnumValue<T>(T value) where T: struct
{
    ThrowIfFalse(typeof(T).IsEnum, "Expected an enum type");
    Violation("Invalid Enum value of Type {0} : {1}", new object[] { typeof(T).Name, value });
}
JaredPar
But the value may be valid, even though the method has not been updated to reflect it. I think the "Invalid value" message is a bit misleading here.
Hosam Aly
@Hosam, but the code was written not expecting that value which is why I chose the name. "Unexpected value" would work just as well I suppose but it's meant to capture that the value is truly not what the programmer intended
JaredPar
@JaredPar: Fair enough. I was thinking more along the lines of "Unhandled value", which makes it pretty clear that it's an unhandled case (IMHO).
Hosam Aly
If the value is valid, then a unit test should cover that code - especially when the method is updated.
SnOrfus
@SnOrfus: yes, but in real life you may forget to update the unit tests (or you may not have the time to do it).
Hosam Aly
A: 

Call it an opinion or a preference, but the idea behind the enum is that it represents the full list of possible values. If an "unexpected value" is being passed around in code, then the enum (or purpose behind the enum) is not up to date. My personal preference is that every enum carry a default assignment of Undefined. Given that the enum is a defined list, it should never be out-of-date with your consuming code.

As far as what to do if your function is getting either an unexpected value or Undefined in my case, a generic answer doesn't seem possible. For me, it depends on the context of the reason for evaluating the enum value: is it a situation where code execution should halt, or can a default value be used?

jro
+3  A: 

My opinion is that since it is a programmer error you should either assert on it or throw a RuntimException (Java, or whatever the equivalent is for other languages). I have my own UnhandledEnumException that extends from RuntimeException that I use for this.

TofuBeer
A: 

It is the responsibility of the calling function to provide valid input and implicitely anything not in the enum is invalid (Pragmatic programmer seems to imply this). That said, this implies that any time you change your enum, you must change ALL code that accepts it as input (and SOME code that yields it as output). But that is probably true anyways. If you have an enum that changes often, you probably should be using something other than an enum, considering that enums are normally compile-time entities.

Brian
I totally agree, but in the real world, a few methods may be missed when the enum is updated, which is why I am asking the question. What do you do in such cases?
Hosam Aly
+2  A: 

The correct program response would be to die in a manner that will allow the developer to easily spot the problem. mmyers and JaredPar both gave good ways to do that.

Why die? That seems so extreme!

The reason being that if you're not handling an enum value properly and just fall through, you're putting your program into an unexpected state. Once you're in an unexpected state, who knows what's going on. This can lead to bad data, errors that are harder to track down, or even security vulnerabilities.

Also, if the program dies, there's a much greater chance that you're going to catch it in QA and thus it doesn't even go out the door.

Gavin Miller
Yes, it seems so extreme! Wouldn't throwing an exception be enough, in the sense that an application can recover from the error? What if the method is not very important? Failure cases would still be captured in QA, as they will produce different results than expected.
Hosam Aly
Great, you recover from your error, but now you don't know what state your program is in. As I stated, this will lead to other subtler problems. QA will see those subtle errors and report them, all the while the root of the problem is not addressed.
Gavin Miller
It's not *me* who recovers from the error, but higher layers in the call hierarchy. This is an important distinction IMHO. If the error was logged (which should be), a good error message would directly lead to the root cause.
Hosam Aly
In most cases the program (and I do mean the calling program) cannot properly recover from the error. If the program knew about the exception and caught/handled it properly, then why would it have passed the unexpected value? It is better for the program to die.
Jeffrey L Whitledge
@Jeff - Well put.
Gavin Miller
A: 

I usually try to define undefined value (0):

enum Mood { Undefined = 0, Happy, Sad }

That way I can always say:

switch (mood)
{
    case Happy: Console.WriteLine("I am happy"); break;
    case Sad:   Console.WriteLine("I am sad"); break;
    case Undefined: // handle undefined case
    default: // at this point it is obvious that there is an unknown problem
             // -> throw -> die :-)
}

This is at least how I usually do this.

David Pokluda
Although I remember having read somewhere in MSDN that one should create an "Undefined" value for an enum, I find this to be a problem for some enums which should not have such value (e.g. FileMode). Unless it's needed, it forces developers to check for it every time they write a method.
Hosam Aly
And the default case still needs to be handled, and that's the point of the question.
Hosam Aly
Well, I mentioned in the comment -- throw and die. I would throw my own exception derived from Exception class.
David Pokluda
Sorry @David, I missed that. The long comment was a bit misleading. :) I have split it on two lines. I hope you don't mind.
Hosam Aly
+1  A: 

This is one of those questions that proves why test driven development is so important.

In this case I'd go for a NotSupportedException because literally the value was unhandled and therefore not supported. A NotImplementedException gives more the idea of: "This is not finished yet" ;)

The calling code should be able to handle a situation like this and unit tests can be created to easily test these kind of situations.

Jeroen Landheer
This is a good point. But sometimes you throw NotSupportedException for valid values, like when creating a StreamReader with a FileMode.Write. I was thinking that the case being discussed is different enough that it shouldn't be handled like other cases.
Hosam Aly
+2  A: 

For C#, something worth knowing is that Enum.IsDefined() is dangerous. You can't rely on it like you are. Getting something not of the expected values is a good case for throwing an exception and dying loudly.

In Java, it's different because enums are classes not integers so you really can't get unexpected values (unless the enum is updated and your switch statement isn't), which is one big reason why I much prefer Java enums. You also have to cater for null values. But getting a non-null case you don't recognize is a good case for throwing an exception too.

cletus
I certainly prefer Java enums to C#. Could you elaborate on which exception you'd throw in the case of unrecognized values?
Hosam Aly
In Java I'd throw an IllegalStateException if I got an enum value I didn't recognize. IllegalArgumentException is OK but to me it seems more like illegal state.
cletus
+5  A: 

I guess most of the above answers are valid, but I'm not sure any are correct.

The correct answer is, you very rarely switch in an OO language, it indicates you are doing your OO wrong. In this case, it's a perfect indication that your Enum class has problems.

You should just be calling Console.WriteLine(mood.moodMessage()), and defining moodMessage for each of the states.

If a new state is added--All Your Code Should Adapt Automatically, nothing should fail, throw an exception or need changes.

Edit: response to comment.

In your example, to be "Good OO" the functionality of the file mode would be controlled by the FileMode object. It could contain a delegate object with "open, read, write..." operations that are different for each FileMode, so File.open("name", FileMode.Create) could be implemented as (sorry about the lack of familiarity with the API):

open(String name, FileMode mode) {
    // May throw an exception if, for instance, mode is Open and file doesn't exist
    // May also create the file depending on Mode
    FileHandle fh = mode.getHandle(name);
    ... code to actually open fh here...
    // Let Truncate and append do their special handling
    mode.setPosition(fh);
}

This is much neater than trying to do it with switches... (by the way, the methods would be both package-private and possibly delegated to "Mode" classes)

When OO is done well, every single method looks like a few lines of really understandable, simple code--TOO simple. You always get the feeling that there is some big messy "Cheese Nucleus" holding together all the little nacho objects, but you can't ever find it--it's nachos all the way down...

Bill K
Just like everything else, enums are good when they are used correctly. For example, if I am designing an API for file handling, I'd make `File.Open(string filename, FileMode mode)` where FileMode is a simple value enum, with no behavior attached to it. I don't see this "doing OO wrong".
Hosam Aly
Woah, I didn't say anything about enums being bad! I LOVE typesafe enumerations!--and the use you specified there is great. It's the switch statement that's problematic. That indicates that a behavior should be moved into the enum.
Bill K
Sorry if I misunderstood, but the question still remains: how do you implement `File.Open` without a switch statement? Talking about myself, I wouldn't give FileMode objects a method to open a file, because it simply doesn't belong there. What do you suggest as an alternative?
Hosam Aly
FileMode, being an enum that is very closely related to file may actually contain the code. More likely it should contain a package level delegate object that has the code to open the file in a given mode.
Bill K
But then what about FileAccess? And FileShare? How do you model combinations of these using delegates?
Hosam Aly
BTW, I'm taking my examples from .NET's File.Open (see http://msdn.microsoft.com/en-us/library/y973b725.aspx). I can't see a good way to model different combinations of these enumerations in an "OO" way.
Hosam Aly
A switch on an enum can always be replaced by a call to a delegated function--they are a transformation of the exact same construct. There is nothing magic about OO, it simply lets you organize your code so that code that should be together is.
Bill K
+1 (would be +2 if it was possible...)
thSoft