5

In my class I have a processor manager that contains a switch case. I have just changed the real name values to Ant, Parrot and Snake.

switch (name)
{
    case "ant":
        return new AntProcessor();
    case "parrot":
        return new ParrotProcessor();
    case "snake":
        return new SnakeProcessor();
    default:
        throw new Exception($"Case {name} not found.");
}

It seems to me that using a switch/case with a string that can take any value is not the best solution, but considering time and development effort it is the best, dirty way to achieve this goal quickly - as is the case here.

My question is about the switch and the way I use the default case to throw an exception. Is this way of throwing an exception correct? Can it be done differently?

6
  • I already asked this part of code review question in code review section but they told me this question belong to here. Commented Dec 17, 2019 at 8:26
  • 2
    Does this answer your question? Should I throw an exception in case of a meaningful value outside of the range or handle it myself?
    – gnat
    Commented Dec 17, 2019 at 8:32
  • I consider than using a switch/case with a string that can take any value is not the best solution but sometime, considering time and development, this is the best dirty way to achieve his goal quickly You do realize that using an enum can be implemented in less than a minute here, right? On top of that, if you use Tab-generation for your switch statement, when using an enum it autogenerates cases for all known enum values, so you're saving more time than it takes to implement the enum.
    – Flater
    Commented Dec 17, 2019 at 9:36
  • Important question: is there any test case which demonstrates that a user of your software can cause this exception to be thrown? If there is, then that is a bug in your program; it should be impossible for such an exception to be thrown. That is the value of this pattern: it provides a target for quality assurance to aim at. You say that this is "quick and dirty", which is another way of saying sloppy, dangerous and probably wrong. See if testing can find a way to trigger this exception! Commented Dec 17, 2019 at 18:56
  • @Flater If he's switching on a piece of text the source of the text is almost certainly external. At some point it has to be converted to the enum--and you have pretty much the same scenario, just moved elsewhere. Commented Dec 20, 2021 at 5:12

4 Answers 4

9

The question as asked is quite meaningless. You shouldn't ask whether to throw an exception or not, you should ask how your software as a whole should react if name = "hobgoblin", for example.

So tell me: Under which circumstances could this code be executed with name = "hobgoblin"? And what should the user see happening in these circumstances? I mean you must have a plan that goes beyond "I'll throw an exception", because someone has to write code that catches the exception and handles it. Or someone will have code that catches the exception and ignores it, in which case throwing the exception isn't very useful. Or nobody catches the exception and your application crashes.

The first thing you need to do is document the behaviour of the function. Document that it will throw an exception, do nothing, assert and crash, return nil, whatever, but document it. Then implement what you documented. And then think about ways to make this safer, either by using an enum that can only have three values, or by declaring that it is legal to call this function with any string, and defining the behaviour for strings that are not handled. In that case you wouldn't throw an exception anymore.

9

tl;dr- Yes, it's generally proper to throw an exception whenever the code attempts to do something that it wasn't designed for, including unexpected defaulting in a switch.


Scenario 1: Dense logic, where all cases ought to be handled properly.

It's pretty common to switch on an enum.

For example, I'd often have code like this:

public enum Things
{
    A,
    B,
    C    
}

public void HandleThing(Things thing)
{
    switch (thing)
    {
        case Things.A:  {  HandleA();    return;                 }
        case Things.B:  {  HandleB();    return;                 }
        case Things.C:  {  HandleC();    return;                 }
        default:        {  throw new NotImplementedException();  }
    }
}

Since the enum's cases are all accounted for, the default branch shouldn't be possible. So, ideally, this method can't throw an exception.

I always include the throw anyway as future-proofing. I figure:

  • If the enum never changes, then it's all good.

  • If the enum does change and the switch is updated to handle the new case, then it's all good.

  • If the enum does change but the switch isn't updated, then throwing an exception is appropriate.


Scenario 2: Sparse logic, where cases are handled opportunistically.

By contrast, say that you have a switch that doesn't necessarily need to perform any work, but rather does something opportunistic.

For example, sometimes in computationally expensive software, e.g. mathematical simulations, you might look for special cases that can be handled more cheaply. If you don't catch a special case, then the more expensive general method will be used instead – which may be slower, but it shouldn't cause the software to malfunction.

In such opportunistic cases, you may want the default to not throw.

That said, you might still include information-logging in an #if DEBUG/#endif pre-processor directive block to record that an unexpected opportunity was missed at design-time. If you do this, you'd probably want to include explicit case-branches for cases that you're aware of but don't want to react to, to avoid falsely triggering the default-branch.

1
  • 3
    Somtimes you even can't avoid a default clause. If in your example HandleThing would return a value, it wouldn't even compile without the default Commented Dec 17, 2019 at 12:29
0

Is this way to throw an exception correct? Can it be done differently?

Depends on your context. How would the exception be handled in the end?

If you just want to display an error message with an interactive UI, just do so, and require to repeat a valid input.

If you have it within a parser, and the input completely screws up your parsers state, then throw an exception and let it stop the whole parsing process.
If parsing can be continued at this state, just add to an error list, that's printed out in the end.

0

Presumably this code is being called from some client which passes a value for the name parameter. The list of possible values and the behavior when a value not in this list is given are part of the contract between client and service. Depending on the requirements, various options are possible:

  • No behavior is specified (i.e. the behavior is undefined): your code may do whatever you like it to do. It might silently return a null value, raise an exception, do an infinite loop...
  • If there is really no reasonable choice, raising an exception would be appropriate. However, the overall user experience may be bad if this exception isn't appropriately handled.
  • Returning a safe default handler may be preferable if your application is required to continue working even in the presence of bad user input or minor programming errors. However, this might mask the presence of such errors which is generally undesirable.

Not the answer you're looking for? Browse other questions tagged or ask your own question.