marți, 4 septembrie 2012

How to eliminate switch in code


1 
Why would you want to eliminate switches, when used appropriately? Please can you elaborate on your question. – RB. Sep 24 '08 at 10:37
I vote this question be made community editable because everybody is saying the same thing and it might be nice to consolidate all the opinions ;) – Josh Sep 24 '08 at 10:50
Switch is not as standard as other instructions. For instance, in C++, you're likely to forget the 'break' and then you'll have unexpected results. Besides, this 'break' is too similar to a GOTO. I have never tried to eliminate switches, but for sure they're not my favourite instruction. ;) – anon Sep 24 '08 at 10:50
What language are you using? – jop Sep 24 '08 at 11:03
I think he's referring to the switch concept and not the C++ or Java switch statement. A switch can also be a chain of 'if-else if' blocks. – Outlaw Programmer Sep 24 '08 at 14:16
show 1 more comment
feedback

23 Answers

up vote81down voteaccepted
Switch-statements are not an antipattern per se, but if you're coding object oriented you should consider if the use of a switch is better solved with polymorphism instead
With polymorphism
foreach (var animal in zoo) {
    switch (typeof(animal)) {
        case "dog":
            echo animal.bark();
            break;

        case "cat":
            echo animal.meow();
            break;
    }
}
becomes
foreach (var animal in zoo) {
    echo animal.speak();
}
share|improve this answer
2 
Polkymorphism? Don't they do that in Barvaria? – Nick Johnson Sep 24 '08 at 10:52
I upmodded it, as it's 100% correct - but please edit the typos! – slim Sep 24 '08 at 10:53
2 
Great example, +1 – Jonas Gulle Sep 26 '08 at 17:35
I being bashed because of similar suggestion in stackoverflow.com/questions/374239/… So many ppl don't believe in polymorphism :) Very good example. – Nazgob Dec 17 '08 at 19:41
feedback
Typically, similar switch statements are scattered throughout a program. If you add or remove a clause in one switch, you often have to find and repair the others too.
Both Refactoring and Refactoring to Patterns have approaches to resolve this.
If your (pseudo) code looks like:
class RequestHandler {

    public void handleRequest(int action) {
        switch(action) {
            case LOGIN:
                doLogin();
                break;
            case LOGOUT:
                doLogout();
                break;
            case QUERY:
               doQuery();
               break;
        }
    }
}
This code violates the Open Closed Principle and is fragile to every new type of action code that comes along. To remedy this you could introduce a 'Command' object:
interface Command {
    public void execute();
}

class LoginCommand implements Command {
    public void execute() {
        // do what doLogin() used to do
    }
}

class RequestHandler {
    private Map<Integer, Command> commandMap; // injected in, or obtained from a factory
    public void handleRequest(int action) {
        Command command = commandMap.get(action);
        command.execute();
    }
}
If your (pseudo) code looks like:
class House {
    private int state;

    public void enter() {
        switch (state) {
            case INSIDE:
                throw new Exception("Cannot enter. Already inside");
            case OUTSIDE:
                 state = INSIDE;
                 ...
                 break;
         }
    }
    public void exit() {
        switch (state) {
            case INSIDE:
                state = OUTSIDE;
                ...
                break;
            case OUTSIDE:
                throw new Exception("Cannot leave. Already outside");
        }
    }
Then you could introduce a 'State' object.
abstract class HouseState {
    public HouseState enter() {
        throw new Exception("Cannot enter");
    }
    public HouseState leave() {
        throw new Exception("Cannot leave");
    }
}
class Inside extends HouseState {
    public HouseState leave() {
        return new Outside();
    }
}
class Outside extends HouseState {
    public HouseState enter() {
        return new Inside();
    }
}

class House {
    private HouseState state;
    public void enter() {
        this.state = this.state.enter();
    }
    public void leave() {
        this.state = this.state.leave();
    }
}
Hope this helps.
share|improve this answer
2 
Thanks for the great example on how to refactor code. Though I might say in the beggining it is a little hard to read (because one has to switch between several files to completely understand it) – rshimoda Oct 29 '08 at 23:51
glad to help :-) – toolkit Oct 30 '08 at 0:20
Upvoted, but please fix typos. ..impelements.. ..psuedo.. – Schalk Versteeg Oct 30 '08 at 14:18
The arguments against switch are valid as long as you realize that the polymorphic solution sacrifices code simplicity. In addition, if you always store your switch cases in enums, some compilers will warn you that states are missing from the switch. – Harvey Dec 22 '08 at 18:32
This is a great example about complete/incomplete operations and of course re-structuring code into OOP. Thanks a ton. I think it will be useful if OOP / Design Patterns proponents suggest treating OOP concepts like operators rather than concepts. I mean that "extends", "factory", "implements", etc are used so often across files, classes, branches. They should be simple as operators like "+", "-", "+=", "?:", "==", "->" etc. When a programmer uses them in his mind as simply as operators, only then he can think across the full class library about program state and (in)complete operations. – namespaceform Jan 22 '10 at 20:23
feedback
Switch in itself isn't that bad, but if you have lots of "switch" or "if/else" on objects in your methods it may be a sign that your design is a bit "procedural" and that your objects are just value buckets. Move the logic to your objects, invoke a method on your objects and let them decide how to respond instead.
share|improve this answer
Assuming of course, that he's not writing in C. :) – Bernard Sep 24 '08 at 10:44
in C, he can (ab?)use function pointers and structs to build something object-like ;) – Tetha Sep 24 '08 at 11:08
You can write FORT^H^H^H^H Java in any language. ;p – Bernard Sep 24 '08 at 12:05
Oh God, I'm a dead man now aren't I? I didn't mean it guys! :D – Bernard Sep 24 '08 at 12:06
feedback
A switch is a pattern, whether implemented with a switch statement, if else chain, lookup table, oop polymorphism, pattern matching or something else.
Do you want to eliminate the use of the "switch statement" or the "switch pattern"? The first one can be eliminated, the second one, only if another pattern/algorithm can be used, and most of the time that is not possible or it's not a better approach to do so.
If you want to eliminate the switch statement from code, the first question to ask is where does it make sense to eliminate the switch statement and use some other technique. Unfortunately the answer to this question is domain specific.
And remember that compilers can do various optimizations to switch statements. So for example if you want to do message processing efficiently, a switch statement is pretty much the way to go. But on the other hand running business rules based on a switch statement is probably not the best way to go and the application should be rearchitected.
Here are some alternatives to switch statement :
share|improve this answer
Could someone give a comparison of message processing using switch versus other alternatives? – Mike A Nov 24 '09 at 8:53
feedback
I think the best way is to use a good Map. Using a dictionary you can map almost any input to some other value/object/function.
your code would look something(psuedo) like this:
void InitMap(){
    Map[key1] = Object/Action;
    Map[key2] = Object/Action;
}

Object/Action DoStuff(Object key){
    return Map[key];
}
share|improve this answer
Depends on the language. It can get a lot less readable than a switch – Vinko Vrsalovic Sep 24 '08 at 10:39
This is a nice elegent solution in the right situation. I did this mapping keycodes recently, and it seemed to read okay for that purpose. – Bernard Sep 24 '08 at 10:40
This is true, I probably wouldn't use this for anything simple, but it does provide some amount of flexibility in terms of configuration over a switch statement. A dictionary can be prepared on the fly while a switch will always be hard coded. – Josh Sep 24 '08 at 10:41
Can be cleaner in some circumstances. Also slower, since it requires function calls. – Nick Johnson Sep 24 '08 at 10:42
If your dictionary is using a decently implemented hash table under the hood then performance degradation should be minimal. – Josh Sep 24 '08 at 10:43
show 3 more comments
feedback
Everybody loves HUGE if else blocks. So easy to read! I am curious as to why you would want to remove switch statements, though. If you need a switch statement, you probably need a switch statement. Seriously though, I'd say it depends on what the code's doing. If all the switch is doing is calling functions (say) you could pass function pointers. Whether it's a better solution is debatable.
Language is an important factor here also, I think.
share|improve this answer
Not everybody. :) – jop Sep 24 '08 at 10:57
1 
Im assuming that was sarcasm :) – Craig Day Sep 24 '08 at 13:57
feedback
if-else
I refute the premise that switch is inherently bad though.
share|improve this answer
feedback
Well, for one, I didn't know using switch was an anti pattern.
Secondly, switch can always be replaced with if / else if statements.
share|improve this answer
exactly - switch is just syntactic methadone for a bunch of if/elsifs. – Mike A Nov 24 '09 at 8:32
feedback
I think that what you are looking for is the Strategy Pattern.
This can be implemented in a number of ways, which have been mentionned in other answers to this question, such as:
  • A map of values -> functions
  • Polymorphism. (the sub-type of an object will decide how it handles a specific process).
  • First class functions.
share|improve this answer
feedback
switch statements would be good to replace if you find yourself adding new states or new behaviour to the statements:
int state;

String getString() {
   switch (state) {
     case 0 : // behaviour for state 0
           return "zero";
     case 1 : // behaviour for state 1
           return "one";
   }
   throw new IllegalStateException();
}

double getDouble() {

   switch (this.state) {
     case 0 : // behaviour for state 0
           return 0d;
     case 1 : // behaviour for state 1
           return 1d;
   }
   throw new IllegalStateException();
}
Adding new behaviour requires copying the switch, and adding new states means adding anothercase to every switch statement.
In Java, you can only switch a very limited number of primitive types whose values you know at runtime. This presents a problem in and of itself: states are being represented as magic numbers or characters.
Pattern matching, and multiple if - else blocks can be used, though really have the same problems when adding new behaviours and new states.
The solution which others have suggested as "polymorphism" is an instance of the State pattern:
Replace each of the states with its own class. Each behaviour has its own method on the class:
IState state;

String getString() {
   return state.getString();
}

double getDouble() {
   return state.getDouble();
}
Each time you add a new state, you have to add a new implementation of the IState interface. In aswitch world, you'd be adding a case to each switch.
Each time you add a new behaviour, you need to add a new method to the IState interface, and each of the implementations. This is the same burden as before, though now the compiler will check that you have implementations of the new behaviour on each pre-existing state.
Others have said already, that this may be too heavyweight, so of course there is a point you reach where you move from one to another. Personally, the second time I write a switch is the point at which I refactor.
share|improve this answer
feedback
Why do you want to? In the hands of a good compiler, a switch statement can be far more efficient than if/else blocks (as well as being easier to read), and only the largest switches are likely to be sped up if they're replaced by any sort of indirect-lookup data structure.
share|improve this answer
1 
At which point you're second-guessing the compiler, and making changes to your design based on compiler internals. Design should follow the nature of the problem, not the nature of the compiler. – Mike A Nov 24 '09 at 8:37
feedback
'switch' is just a language construct and all language constructs can be thought of as tools to get a job done. As with real tools, some tools are better suited to one task than another (you wouldn't use a sledge hammer to put up a picture hook). The important part is how 'getting the job done' is defined. Does it need to be maintainable, does it need to be fast, does it need to scale, does it need to be extendable and so on.
At each point in the programming process there are usually a range of constructs and patterns that can be used: a switch, an if-else-if sequence, virtual functions, jump tables, maps with function pointers and so on. With experience a programmer will instinctively know the right tool to use for a given situation.
It must be assumed that anyone maintaining or reviewing code is at least as skilled as the original author so that any construct can be safely used.
share|improve this answer
OK, but why do we need 5 different, redundant ways of doing the same thing - conditional execution? – Mike ANov 24 '09 at 8:43
@mike.amy: Because each method has different benefits and costs and it's all about getting the most benefit with the least cost. – Skizz Nov 24 '09 at 9:18
feedback
What about cyclomatic complexity?
share|improve this answer
what about common sense? reducing cyclomatic complexity doesn't mean "kill all switches". May be it would be better to reformulate your question? For example "When to not use switch statement?" – aku Sep 24 '08 at 10:48
A program written using switch and program that performs the same action and written using say, virtual functions, must surely have the same cyclomatic complexity? – Skizz Sep 24 '08 at 10:48
Skizz, nope. cyclomatic complexity can be reduced by reducing number of if/switches. For example by using map(dictionary) of functions instead of N switch cases. But question is worded quite strange IMO – aku Sep 24 '08 at 10:52
Is it possible to turn this post into a community editable post so everybody's answers can be consolidated into an informative post about the pros/cons of using switch statements and how to refactor one into something like a Map/Dictionary. – Josh Sep 24 '08 at 10:59
I am an spanish speaker, maybe that´s why "the question is worded quite strange". Thank you all – MarianoSep 24 '08 at 11:30
show 2 more comments
feedback
If the switch is there to distinguish between various kinds of objects, you're probably missing some classes to precisely describe those objects, or some virtual methods...
share|improve this answer
feedback
For C++
If you are referring to ie an AbstractFactory I think that a registerCreatorFunc(..) method usually is better than requiring to add a case for each and every "new" statement that is needed. Then letting all classes create and register a creatorFunction(..) which can be easy implemented with a macro (if I dare to mention). I believe this is a common approach many framework do. I first saw it in ET++ and I think many frameworks that require a DECL and IMPL macro uses it.
share|improve this answer
feedback
Use a language that doesn't come with a built-in switch statement. Perl 5 comes to mind.
Seriously though, why would you want to avoid it? And if you have good reason to avoid it, why not simply avoid it then?
share|improve this answer
feedback
The most obvious, language independent, answer is to use a series of 'if'.
If the language you are using has function pointers (C) or has functions that are 1st class values (Lua) you may achieve results similar to a "switch" using an array (or a list) of (pointers to) functions.
You should be more specific on the language if you want better answers.
share|improve this answer
feedback
Switch statements can often be replaced by a good OO design.
For example, you have an Account class, and are using a switch statement to perform a different calculation based on the type of account.
I would suggest that this should be replaced by a number of account classes, representing the different types of account, and all implementing an Account interface.
The switch then becomes unnecessary, as you can treat all types of accounts the same and thanks to polymorphism, the appropriate calculation will be run for the account type.
share|improve this answer
feedback
Depends why you want to replace it!
Many interpreters use 'computed gotos' instead of switch statements for opcode execution.
What I miss about C/C++ switch is the Pascal 'in' and ranges. I also wish I could switch on strings. But these, while trivial for a compiler to eat, are hard work when done using structures and iterators and things. So, on the contrary, there are plenty of things I wish I could replace with a switch, if only C's switch() was more flexible!
share|improve this answer
feedback
In a procedural language, like C, then switch will be better than any of the alternatives.
In an object-oriented language, then there are almost always other alternatives available that better utilise the object structure, particularly polymorphism.
The problem with switch statements arises when several very similar switch blocks occur at multiple places in the application, and support for a new value needs to be added. It is pretty common for a developer to forget to add support for the new value to one of the switch blocks scattered around the application.
With polymorphism, then a new class replaces the new value, and the new behaviour is added as part of adding the new class. Behaviour at these switch points is then either inherited from the superclass, overridden to provide new behaviour, or implemented to avoid a compiler error when the super method is abstract.
Where there is no obvious polymorphism going on, it can be well worth implementing the Strategy pattern.
But if your alternative is a big IF ... THEN ... ELSE block, then forget it.
share|improve this answer
feedback
Function pointers are one way to replace a huge chunky switch statement, they are especially good in languages where you can capture functions by their names and make stuff with them.
Of course, you ought not force switch statements out of your code, and there always is a chance you are doing it all wrong, which results with stupid redundant pieces of code. (This is unavoidable sometimes, but a good language should allow you to remove redundancy while staying clean.)
This is a great divide&conquer example:
Say you have an interpreter of some sort.
switch(*IP) {
    case OPCODE_ADD:
        ...
        break;
    case OPCODE_NOT_ZERO:
        ...
        break;
    case OPCODE_JUMP:
        ...
        break;
    default:
        fixme(*IP);
}
Instead, you can use this:
opcode_table[*IP](*IP, vm);

... // in somewhere else:
void opcode_add(byte_opcode op, Vm* vm) { ... };
void opcode_not_zero(byte_opcode op, Vm* vm) { ... };
void opcode_jump(byte_opcode op, Vm* vm) { ... };
void opcode_default(byte_opcode op, Vm* vm) { /* fixme */ };

OpcodeFuncPtr opcode_table[256] = {
    ...
    opcode_add,
    opcode_not_zero,
    opcode_jump,
    opcode_default,
    opcode_default,
    ... // etc.
};
Note that I don't know how to remove the redundancy of the opcode_table in C. Perhaps I should make a question about it. :)
share|improve this answer
feedback
Switch is not a good way to go as it breaks the Open Close Principal. This is how I do it.
public class Animal
{
       public abstract void Speak();
}


public class Dog : Animal
{
   public virtual void Speak()
   {
       Console.WriteLine("Hao Hao");
   }
}

public class Cat : Animal
{
   public virtual void Speak()
   {
       Console.WriteLine("Meauuuu");
   }
}
And here is how to use it (taking your code):
foreach (var animal in zoo) 
{
    echo animal.speak();
}
Basically what we are doing is delegating the responsibility to the child class instead of having the parent decide what to do with children.
You might also want to read up on "Liskov Substitution Principle".
share|improve this answer
feedback
Another vote for if/else. I'm not a huge fan of case or switch statements because there are some people that don't use them. The code is less readable if you use case or switch. Maybe not less readable to you, but to those that have never needed to use the command.
The same goes for object factories.
If/else blocks are a simple construct that everyone gets. There's a few things you can do to make sure that you don't cause problems.
Firstly - Don't try and indent if statements more than a couple of times. If you're finding yourself indenting, then you're doing it wrong.
 if a = 1 then 
     do something else 
     if a = 2 then 
         do something else
     else 
         if a = 3 then 
             do the last thing
         endif
     endif 
  endif
Is really bad - do this instead.
if a = 1 then 
   do something
endif 
if a = 2 then 
   do something else
endif 
if a = 3 then 
   do something more
endif
Optimisation be damned. It doesn't make that much of a difference to the speed of your code.
Secondly, I'm not averse to breaking out of an If Block as long as there are enough breaks statements scattered through the particular code block to make it obvious
procedure processA(a:int)
    if a = 1 then 
       do something
       procedure_return
    endif 
    if a = 2 then 
       do something else
       procedure_return
    endif 
    if a = 3 then 
       do something more
       procedure_return
    endif 
end_procedure
EDIT: On Switch and why I think it's hard to grok:
Here's an example of a switch statement...
private void doLog(LogLevel logLevel, String msg) {
   String prefix;
   switch (logLevel) {
     case INFO:
       prefix = "INFO";
       break;
     case WARN:
       prefix = "WARN";
       break;
     case ERROR:
       prefix = "ERROR";
       break;
     default:
       throw new RuntimeException("Oops, forgot to add stuff on new enum constant");
   }
   System.out.println(String.format("%s: %s", prefix, msg));
 }
For me the issue here is that the normal control structures which apply in C like languages have been completely broken. There's a general rule that if you want to place more than one line of code inside a control structure, you use braces or a begin/end statement.
e.g.
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
For me (and you can correct me if I'm wrong), the Case statement throws this rule out of the window. A conditionally executed block of code is not placed inside a begin/end structure. Because of this, I believe that Case is conceptually different enough to not be used.
Hope that answers your questions.
share|improve this answer
Wow - obviously a contentious answer. I'd be interested in knowing what I got so wrong. – seanyboy Sep 24 '08 at 11:17
Er, switch as being too complex? I don't know... seems to me there wouldn't be many language features left you could use. :) Also, in your central example, wouldn't it be smarter to do if (a==1 || a==2 || a==3) do something? – Lars Westergren Sep 24 '08 at 11:17
Also, in your last example, "break" won't do anything in most languages-that breaks out of the nearest block (usually a loop), which in your case happens anyway on the next line (endif). If you use a language where break is "return", many returns are also frowned upon (except for "guard statements") – Lars Westergren Sep 24 '08 at 11:31
1 
"Optimisation be damned. It doesn't make that much of a difference to the speed of your code." My code runs on a mobile platform. Optimizations matter. Further, switches can make code look VERY clean (compared to if..elseif..elseif..elseif...) if used correctly. Never seen them? Learn them. – Swati Sep 24 '08 at 12:10
Switch isn't complicated at all, but I've always tried to minimise the number of constructs used in code to reduce friction in understanding. – seanyboy Sep 24 '08 at 12:26
show 3 more comments

Niciun comentariu:

Trimiteți un comentariu