16

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
Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
  • I am posting an answer with my own opinion. If you have a different opinion, or a different case that supports this one, please share your knowledge. The purpose of the question is to gather ideas about the topic, to help other people (and me) facing this situation. – Hosam Aly Mar 06 '09 at 18:43
  • @Oscar: It should be a language-independent question IMHO. People can find the answer for both languages here. – Hosam Aly Mar 06 '09 at 19:05
  • Enum validation looks weird to me. I don't use java, may be that's why. Could you please describe a situation when that code can throw ArgumentOutOfRangeException? – DK. Mar 06 '09 at 21:53
  • @DK: In Java this would never happen, but in C# it can happen, because enumerations can be cast back and forth to integers. So for example, a call like `PrintMood((Mood)10)` is valid in C#, but the value being passed is not valid as far as the method is concerned. – Hosam Aly Mar 06 '09 at 23:02
  • Downvoters: comments welcome! – Hosam Aly Mar 06 '09 at 23:03
  • This is a question I accidentally learned from, which I like. I used to throw `AssertionError`s, because a wrong assertion is exactly what I'm having when the `switch` misses out on enum values. After reading Bill K's answer, however, I'm going with his approach. – Hanno Fietz Dec 12 '11 at 15:21

12 Answers12

11

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
  • 62,186
  • 18
  • 105
  • 157
  • 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 Mar 06 '09 at 23:14
  • 2
    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 Mar 06 '09 at 23:24
  • 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 Mar 06 '09 at 23:35
  • 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 Mar 06 '09 at 23:42
  • But then what about FileAccess? And FileShare? How do you model combinations of these using delegates? – Hosam Aly Mar 07 '09 at 06:30
  • 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 Mar 07 '09 at 09:19
  • 2
    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 Mar 09 '09 at 16:41
  • Wish I could downvote, as this answer is quite unhelpful. Enums exist all over the place in code that we cannot refactor and we have to deal with them. – Joe Strommen Nov 10 '11 at 17:52
  • @Joe You don't see too many enums in library code (Library code is a different problem completely), non-library code you should be able to edit. On top of that, if you look at your library Enums you will find that you rarely if ever have to iterate over them, again it's just the nature of the problem spaces that make things turn out this way. I welcome counter-examples to discuss. – Bill K Nov 10 '11 at 22:45
  • Liked your "Cheese Nucleus" idea. So you're saying, well done OO looks disturbing? :) – Hanno Fietz Dec 12 '11 at 15:24
  • Looks disturbingly simple, yeah. We programmers are trained to search out the hidden huge complex procedure that drives the whole system and when it's not there it's kind of unnerving. – Bill K Feb 04 '15 at 23:55
10

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.

Note: NotImplementedException exists in commons.lang.

Jim G.
  • 15,141
  • 22
  • 103
  • 166
Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
  • 1
    @SnOrfus: Why? It's a perfectly valid answer. – Michael Myers Mar 06 '09 at 22:20
  • @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 Mar 06 '09 at 23:05
  • I am choosing this as the accepted answer, because currently it is the highest voted one. – Hosam Aly Mar 09 '09 at 10:29
  • 1
    @JimG. Commons Lang has it: http://commons.apache.org/proper/commons-lang/javadocs/api-2.0/org/apache/commons/lang/NotImplementedException.html Or you could throw an `UnsupportedOperationException`. Reference: http://stackoverflow.com/a/2329389 – Hosam Aly Feb 09 '14 at 23:42
7

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
  • 188,989
  • 46
  • 291
  • 292
  • 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 Mar 06 '09 at 19:13
  • 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 Mar 06 '09 at 19:30
  • 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 Mar 06 '09 at 19:31
  • 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 Mar 06 '09 at 20:02
  • 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 Mar 06 '09 at 20:40
3

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
  • 60,850
  • 18
  • 118
  • 163
3

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.

Community
  • 1
  • 1
Gavin Miller
  • 43,168
  • 21
  • 122
  • 188
  • 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 Mar 06 '09 at 19:33
  • 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 Mar 06 '09 at 19:47
  • 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 Mar 06 '09 at 20:04
  • 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 Mar 06 '09 at 22:22
  • Extreme yes, but I'm surprised of how reasonable this sounds to me. – Hanno Fietz Dec 12 '11 at 15:27
  • -1. If you write your server code like this, then you have enabled a person who finds this bug to take down your service. – tster Jul 30 '13 at 16:49
  • @tster writing code where a single request takes down your entire server is probably not the greatest idea... raise a 500 error instead. – Gavin Miller Jul 30 '13 at 19:26
  • @GavinMiller, yes, which is why dying is a bad idea. In most frameworks throwing an exception will result in a 500. Dying will result in a closed connection, and no more process. – tster Jul 31 '13 at 20:44
2

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
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 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 Mar 06 '09 at 19:03
  • @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 Mar 06 '09 at 19:14
  • @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 Mar 06 '09 at 19:18
  • If the value is valid, then a unit test should cover that code - especially when the method is updated. – Steven Evers Mar 06 '09 at 22:03
  • @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 Mar 06 '09 at 23:06
2

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.

Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
cletus
  • 616,129
  • 168
  • 910
  • 942
  • I certainly prefer Java enums to C#. Could you elaborate on which exception you'd throw in the case of unrecognized values? – Hosam Aly Mar 06 '09 at 23:10
  • 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 Mar 07 '09 at 00:35
1

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
  • 1,253
  • 8
  • 8
1

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
  • 9,160
  • 2
  • 36
  • 43
  • 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 Mar 06 '09 at 19:58
0

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
  • 7,372
  • 2
  • 23
  • 36
0

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
  • 25,523
  • 18
  • 82
  • 173
  • 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 Mar 06 '09 at 19:20
0

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.

Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
David Pokluda
  • 10,693
  • 5
  • 28
  • 26
  • 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 Mar 06 '09 at 20:11
  • 1
    And the default case still needs to be handled, and that's the point of the question. – Hosam Aly Mar 06 '09 at 20:11
  • Well, I mentioned in the comment -- throw and die. I would throw my own exception derived from Exception class. – David Pokluda Mar 06 '09 at 21:06
  • 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 Mar 06 '09 at 21:53