views:

703

answers:

13

I've come across a switch statement in the codebase I'm working on and I'm trying to figure out how to replace it with something better since switch statements are considered a code smell. However, having read through several posts on stackoverflow about replacing switch statements I can't seem to think of an effective way to replace this particular switch statement.

Its left me wondering if this particular switch statement is ok and if there are particular circumstances where switch statements are considered appropriate.

In my case the code (slightly obfuscated naturally) that I'm struggling with is like this:

private MyType DoSomething(IDataRecord reader)
{
    var p = new MyType
                {
                   Id = (int)reader[idIndex],
                   Name = (string)reader[nameIndex]
                }

    switch ((string) reader[discountTypeIndex])
    {
        case "A":
            p.DiscountType = DiscountType.Discountable;
            break;
        case "B":
            p.DiscountType = DiscountType.Loss;
            break;
        case "O":
            p.DiscountType = DiscountType.Other;
            break;
    }

    return p;
}

Can anyone suggest a way to eliminate this switch? Or is this an appropriate use of a switch? And if it is, are there other appropriate uses for switch statements? I'd really like to know where they are appropriate so I don't waste too much time trying to eliminate every switch statement I come across just because they are considered a smell in some circumstances.

Update: At the suggestion of Michael I did a bit of searching for duplication of this logic and discovered that someone had created logic in another class that effectively made the whole switch statement redundant. So in the context of this particular bit of code the switch statement was unnecessary. However, my question is more about the appropriateness of switch statements in code and whether we should always try to replace them whenever they are found so in this case I'm inclined to accept the answer that this switch statement is appropriate.

+14  A: 

This is an appropriate use for a switch statment, as it makes the choices readable, and easy to add or subtract one.

See this link.

Lance Roberts
+1 Thanks for the link Lance...was a very good read.
mezoid
I'm not sure the switch statement should be buried in the DoSomething() method though...
Daniel Paull
+1  A: 

I wouldn't use an if. An if would be less clear than the switch. The switch is telling me that you are comparing the same thing throughout.

Just to scare people, this is less clear than your code:

if (string) reader[discountTypeIndex]) == "A")
   p.DiscountType = DiscountType.Discountable;
else if (string) reader[discountTypeIndex]) == "B")
   p.DiscountType = DiscountType.Loss;
else if (string) reader[discountTypeIndex]) == "O")
   p.DiscountType = DiscountType.Other;

This switch may be OK, you might want to look at @Talljoe suggestion.

jrcs3
+2  A: 

This switch statement is fine. Do you guys not have any other bugs to attend to? lol

However, there is one thing I noticed... You shouldn't be using index ordinals on the IReader[] object indexer.... what if the column orders change? Try using field names i.e. reader["id"] and reader["name"]

bbqchickenrobot
Its not obvious from my code snippet but the code does use the field names....discountTypeIndex is assigned in a method called PopulateOrdinals as follows: discountTypeIndex = reader.GetOrdinal("discount_type")
mezoid
Calling GetOrdinal() once and using the index over and over (like @mezoid is doing) is more efficient than calling the string indexer each time. Whether it's worth the effort depends on your situation and how many records you expect to return.
Talljoe
+1  A: 

Are switches on discount type located throughout your code? Would adding a new discount type require you to modify several such switches? If so you should look into factoring the switch out. If not, using a switch here should be safe.

If there is a lot of discount specific behavior spread throughout your program, you might want to refactor this like:

p.Discount = DiscountFactory.Create(reader[discountTypeIndex]);

Then the discount object contains all the attributes and methods related to figuring out discounts.

Michael
At the moment I'm not aware of it being used anywhere else...but that's because I'm unfamiliar with this particular section of the codebase...I'll keep your suggestion in mind if I see if duplicated elsewhere...
mezoid
+10  A: 

Switch statements (especially long ones) are considered bad, not because they are switch statements, but because their presence suggests a need to refactor.

The problem with switch statements is they create a bifurcation in your code (just like an if statement does). Each branch must be tested individually, and each branch within each branch and... well, you get the idea.

That said, the following article has some good practices on using switch statements:

http://elegantcode.com/2009/01/10/refactoring-a-switch-statement/

In the case of your code, the article in the above link suggests that, if you're performing this type of conversion from one enumeration to another, you should put your switch in its own method, and use return statements instead of the break statements. I've done this before, and the code looks much cleaner:

private DiscountType GetDiscountType(string discount)
{
    switch (discount)
    {
        case "A": return DiscountType.Discountable;
        case "B": return DiscountType.Loss;
        case "O": return DiscountType.Other;
    }
}
Robert Harvey
Curious to see how one would refactor this if you are of the opinion that switches are bad or suggest a refactor, I believe that was his original question...
bbqchickenrobot
The article I provided in my answer has an example of just such a refactoring. But the real refactoring occurs when you can redesign the code in a way that the switch statement is no longer needed. That is a case-by-case thing. Some switch statements are a code smell because they exist due to a poor design.
Robert Harvey
Can you just omit the break; statement like that?
scottm
+1 Thanks for the link. I'll definitely consider your suggestion of moving the switch statement to a method in future code.
mezoid
Yes you can eliminate the break statement. In fact, the compiler will complain if you don't, because it is unreachable code.
Robert Harvey
The break statement is not needed here because the return statements substitute for it.
Robert Harvey
+3  A: 

I think changing code for the sake of changing code is not best use of ones time. Changing code to make it [ more readable, faster, more efficient, etc, etc] makes sense. Don't change it merely because someone says you're doing something 'smelly'.

-Rick

Rick
+1 for this... exactly, if switches were bad i think near genius C# creator(s) would have left it out. Just as the did with Multiple Inheritance. Of course, like anything, including generics or Interfaces one can completely abuse and/or overuse a concept.
bbqchickenrobot
@Rick I agree with you. However the reason I'm refactoring this method is because I felt other parts of the method (which I didn't show in my snippet for the sake of brevity) could do with a bit of cleaning up and this switch was just one of the items on my list of thinks to consider for cleanup...
mezoid
+2  A: 

In my opinion, it's not switch statements that are the smell, it's what's inside them. This switch statement is ok, to me, until it starts adding a couple of more cases. Then it may be worth creating a lookup table:

private static Dictionary<string, DiscountType> DiscountTypeLookup = 
  new Dictionary<string, DiscountType>(StringComparer.Ordinal)
    {
      {"A", DiscountType.Discountable},
      {"B", DiscountType.Loss},
      {"O", DiscountType.Other},
    };

Depending on your point-of-view, this may be more or less readable.

Where things start getting smelly is if the contents of your case are more than a line or two.

Talljoe
A: 

I think this depends if you are creating MType add many different places or only at this place. If you are creating MType at many places always having to switch for the dicsount type of have some other checks then this could be a code smell.

I would try to get the creation of MTypes in one single spot in your program maybe in the constructor of the MType itself or in some kind of factory method but having random parts of your program assign values could lead to somebody not knowing how the values should be and doing something wrong.

So the switch is good but maybe the switch needs to be moved more inside the creation part of your Type

Janusz
+1  A: 

Yes, this looks like a correct usage of switch statement.

However, I have another question for you.

Why haven't you included the default label? Throwing an Exception in the default label will make sure that the program will fail properly when you add a new discountTypeIndex and forget to modify the code.

Also, if you wanted to map a string value to an Enum, you can use Attributes and reflection.

Something like:

public enum DiscountType
{
 None,

 [Description("A")]
 Discountable,

 [Description("B")]
 Loss,

 [Description("O")]
 Other
}

public GetDiscountType(string discountTypeIndex)
{
 foreach(DiscountType type in Enum.GetValues(typeof(DiscountType))
 {
        //Implementing GetDescription should be easy. Search on Google.
  if(string.compare(discountTypeIndex, GetDescription(type))==0)
   return type;
 }

 throw new ArgumentException("DiscountTypeIndex " + discountTypeIndex + " is not valid.");
}
SolutionYogi
A: 

When you design a language and finally have a chance to remove the ugliest, most non-intuitive error prone syntax in the whole language.

THAT is when you try and remove a switch statement.

Just to be clear, I mean the syntax. This is something taken from C/C++ which should have been changed to conform with the more modern syntax in C#. I wholeheartedly agree with the concept of providing the switch so the compiler can optimise the jump.

Spence
A: 

I'm not absolutely opposed to switch statements, but in the case you present, I'd have at least eliminated the duplication of assigning the DiscountType; I might have instead written a function that returns a DiscountType given a string. That function could have simply had the return statements for each case, eliminating the need for a break. I find the need for breaks between switch cases very treacherous.

private MyType DoSomething(IDataRecord reader)
{
    var p = new MyType
                {
                   Id = (int)reader[idIndex],
                   Name = (string)reader[nameIndex]
                }

    p.DiscountType = FindDiscountType(reader[discountTypeIndex]);

    return p;
}

private DiscountType FindDiscountType (string key) {
    switch ((string) reader[discountTypeIndex])
    {
        case "A":
            return DiscountType.Discountable;
        case "B":
            return DiscountType.Loss;
        case "O":
            return DiscountType.Other;
    }
    // handle the default case as appropriate
}

Pretty soon, I'd have noticed that FindDiscountType() really belongs to the DiscountType class and moved the function.

Carl Manaster
+1  A: 

Robert Harvey and Talljoe have provided excellent answers - what you have here is a mapping from a character code to an enumerated value. This is best expressed as a mapping where the details of the mapping are provided in one place, either in a map (as Talljoe suggests) or in a function that uses a switch statement (as suggested by Robert Harvey).

Both of those techniques are probably fine in this case, but I'd like to draw your attention to a design principal that may be useful here or in other similar cases. The the Open/Closed principal:

If the mapping is likely to change over time, or possibly be extended runtime (eg, through a plugin system or by reading the parts of the mapping from a database), then a using the Registry Pattern will help you adhere to the open/closed principal, in effect allowing the mapping to be extended without affecting any code that uses the mapping (as they say - open for extension, closed for modification).

I think this is a nice article on the Registry Pattern - see how the registry holds a mapping from some key to some value? In that way it's similar to your mapping expressed as a switch statement. Of course, in your case you will not be registering objects that all implement a common interface, but you should get the gist:

So, to answer the original question - the case statement is poor form as I expect the mapping from the character code to an enumerated value will be needed in multiple places in your application, so it should be factored out. The two answers I referenced give you good advice on how to do that - take your pick as to which you prefer. If, however, the mapping is likely to change over time, consider the Registry Pattern as a way insulating your code from the effects of such change.

Daniel Paull
+1  A: 

You are right to suspect this switch statement: any switch statement that is contingent on the type of something may be indicative of missing polymorphism (or missing subclasses).

TallJoe's dictionary is a good approach, however.

Note that if your enum and database values were integers instead of strings, or if your database values were the same as the enum names, then reflection would work, e.g. given

public enum DiscountType : int
{
    Unknown = 0,
    Discountable = 1,
    Loss = 2,
    Other = 3
}

then

p.DiscountType = Enum.Parse(typeof(DiscountType), 
    (string)reader[discountTypeIndex]));

would suffice.

Steven A. Lowe