Reputation: 17454
Often in school, our lecturers will tell us to always include a Default
statement at the end of a switch case statement. However, I have always been wondering is it necessary for ALL (or most) scenario?
Consider the following example in C++:
int num = rand()%3;
switch (num)
{
case 0: methodOne();
break;
case 1: methodTwo();
break;
case 2: methodThree();
break;
}
In the case above, I feel that it is impossible to have a case where it can be > 2 or < 0, so do I still need to include a Default
statement?
There are similar questions in SO asking for the need of Default
in switch-case. The replies given there stated that we should almost at anytime include a Default
. But of all the cases I encountered personally, it just seems to be redundant since the Default
can never be reached.
Edit: Also, in terms of defensive programming, does this scenario needs a Default
statement?
And if I were to add a Default
statement. It will only be a error-handling
statement, am I right to say that?
Upvotes: 4
Views: 175
Reputation: 16419
Technically, no you don't, because you've covered all possible cases with your switch statement.
However, I always find it useful to include an assertion/exception in the default anyway. Consider the following scenario:
// V1.0.0: Initial version.
int num = rand()%3;
switch (num)
{
case 0: methodOne();
break;
case 1: methodTwo();
break;
case 2: methodThree();
break;
}
Later...
// V1.0.0: Initial version.
// V1.0.1: Added a fourth method.
int num = rand()%4;
switch (num)
{
case 0: methodOne();
break;
case 1: methodTwo();
break;
case 2: methodThree();
break;
}
In this scenario, developer #2 updated the rand
modulus, but didn't actually add the case to handle num == 4
. Without the default
, you're going to silently fail, and that could cause all kinds of badness that could be very hard to debug. A more maintainable solution might be:
// V1.0.0: Initial version.
// V1.0.1: Added a fourth method.
int num = rand()%4;
switch (num)
{
case 0: methodOne();
break;
case 1: methodTwo();
break;
case 2: methodThree();
break;
default:
assert(false);
throw InvalidNumException("BUG: Method has not been specified for value of num");
}
When debugging, this would stop the debugger at the assert, and if (god forbid) the missing case
makes it all the way to production, you'll get an exception thrown, rather than just running off and doing stuff that shouldn't happen.
EDIT:
I think including a catch-all is a good addition to a defensive programming style. It guarantees that you'll get a useful outcome if you miss a case
statement (even if that useful outcome is to cause the program to crash).
EDIT 2:
As Andre Kostur mentioned in a comment to this answer, some compilers will emit warnings if you switch on an enum and forget to handle a case
, which is a good reason to not include a default
case for an enum switch statement. Refer to Phresnel's answer for more information about that.
Upvotes: 6
Reputation: 254431
do I still need to include a
default
statement?
You don't need to, as long as the assumption about the possible range of num
holds - which it will, as long as you don't change the code that calculates it.
You might want to, to validate that assumption in case it changes in future. That kind of defensive programming can be useful to catch broken assumptions quickly, before they cause major problems.
And if I were to add a Default statement. It will only be a error-handling statement, am I right to say that?
Yes. I'd throw a logic_error
, or maybe just terminate the program, to indicate that a logical assumption is invalid.
Upvotes: 2
Reputation: 39089
Depending on the exact context, it can be a matter of style. What I do sometimes is the following.
Consider that you in some point of time you tweak
int num = rand()%3;
to
int num = rand()%4;
then your switch statement is not correct and complete anymore. For such cases, you could add the following:
default:
throw std::logic_error("Oh noes.");
std::logic_error
is for errors by the programming team. Now if your team forgets to update the switch
, it will (hopefully early) have an aborting program with a trace to hunt down for.
default
There is also a downside of including a default
-clause. When you switch
upon an enum
...
enum class Color {
Red, Green, Blue
};
....
Color c = ....;
switch(c) {
case Color::Red: break;
case Color::Green: break;
};
... some compilers will warn you that not all cases are covered. To shut the compiler up, you can now do two things:
Color c = ....;
switch(c) {
case Color::Red: break;
case Color::Green: break;
default: break;
};
Color c = ....;
switch(c) {
case Color::Red: break;
case Color::Green: break;
case Color::Blue: break;
};
You will realise that of those two alternatives, the latter might be more productive. However, this relies on compiler behaviour. You could still throw an exception in the default, but transform what would be a nice compile time error into a runtime error, of which many agree the former is preferable.
The best of two worlds (portable errors, with the bonus of compile time errors) could be achieved using early exit or structures where you can test afterwards if a case was hit:
Color c = ....;
switch(c) {
case Color::Red: return;
case Color::Green: return;
};
throw std::logic_error(...);
MYSQL mysql = {0};
switch(c) {
case Color::Red: mysql = red_database(); break;
case Color::Green: mysql = green_database(); break;
};
if (!mysql)
throw std::logic_error(...);
Upvotes: 2
Reputation: 60065
So let's evolve your code a little bit, to something more realistic:
void processNumber(int maxNum) {
int num = rand()%maxNum;
switch (num)
{
case 0: methodOne();
break;
case 1: methodTwo();
break;
case 2: methodThree();
break;
}
}
and here you need to make sure that it is in the set of allowed values [1, 2, 3]
. You can check it in number of ways, but you need safeguards and check input carefully and raise errors, even if it is internal function.
Upvotes: 0
Reputation: 5844
Strictly speaking, you do not. However, in larger projects it can help finding or avoiding bugs if you want to change your code later.
For example, imagine you want to add some cases/methods later. If you leave the switch
as is, you might have to debug and see why the new method isn't called. On the other hand, throwing a kind of NotImplementedException
there will directly lead you to the fact that you forgot to add a case.
Upvotes: 2
Reputation: 6658
its not necessary, but it is a good habit to include it, with a print. I always do something like print "This should NOT happen"
in the default (or except) so I know there is something happening which I didnt expect to happen. Computers can sometimes do weird things, be prepared!
Upvotes: 3