Eliminating `switch` statements [closed]
Asked Answered
S

23

202

What are ways of eliminating the use of switch statements in code?

Shebat answered 24/9, 2008 at 10:34 Comment(5)
Why would you want to eliminate switches, when used appropriately? Please can you elaborate on your question.Multimillionaire
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 ;)Toot
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. ;)Repatriate
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.Crissie
You may be amused: there are a large number of notable programmers from the agile community joinng the anti-IF campaign: antiifcampaign.com/supporters.htmlOverage
B
291

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 of using a switch statement.

With polymorphism, this:

foreach (var animal in zoo) {
    switch (typeof(animal)) {
        case "dog":
            echo animal.bark();
            break;

        case "cat":
            echo animal.meow();
            break;
    }
}

becomes this:

foreach (var animal in zoo) {
    echo animal.speak();
}
Bronson answered 24/9, 2008 at 10:49 Comment(6)
I being bashed because of similar suggestion in #374739 So many ppl don't believe in polymorphism :) Very good example.Aromatize
-1: I've never used a switch statement with typeof, and this answer doesn't suggest ways or reasons to work around switch statements in other situations.Ovotestis
I agree with @Ovotestis - given example doesn't really show how to eliminate switch by polymorphism. The simple examples: get name of enum by switching it's values, or perform some code by appropriate values in some kind of algorithm.Czarism
I was wondering how to eliminate it before you can rely con specific implementations, i.e. in a factory. And I found a great example here https://mcmap.net/q/49051/-creating-a-factory-method-in-java-that-doesn-39-t-rely-on-if-elseFaithfaithful
very trivial example.Finger
Refactoring the switch cases by polymorphism really helps in ease while application further development and during maintaince. Also in case we have more cases or previous cases being modified then also existing code with business logic does not nwed to be changed. #40452365Blairblaire
E
266

See the Switch Statements Smell:

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();
    }
}
Engdahl answered 24/9, 2008 at 10:54 Comment(10)
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)Grundyism
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.Heterogamy
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.Seve
I am starting to think that SWITCH is much more understandable and logical, compared to this. And I usually like OOP very much, but this resolution seems too abstract for meHoang
With the Command object, would the code to generate Map<Integer, Command> not need a switch?Darfur
@Darfur No, you would just do map.add(i => someCommand) for each command you wish to add. This gives you (and other classes) flexibility in adding additional commands. But for fairly static stuff, I think swtich statements are cleaner and more obvious...Curmudgeon
But when you see 10 switch case statements or more it becomes obviously problematic.Leclair
Every time I see a long switch code, it reminds me of the QEMU source code of translate, which is a nightmare when I'm trying to understand the code.Kroll
For HouseState why did you create abstract class instead of interface?Symbolic
Make action an enum that implements commadCaldeira
M
46

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 :

Morgun answered 24/9, 2008 at 10:49 Comment(1)
Could someone give a comparison of message processing using switch versus other alternatives?Overage
L
38

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.

Lunulate answered 24/9, 2008 at 10:41 Comment(4)
Assuming of course, that he's not writing in C. :)Spicate
in C, he can (ab?)use function pointers and structs to build something object-like ;)Fils
You can write FORT^H^H^H^H Java in any language. ;pSpicate
Fully agree - switch is great way to reduce code lines, but don't play with it too mach.Czarism
T
22

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];
}
Toot answered 24/9, 2008 at 10:36 Comment(9)
Depends on the language. It can get a lot less readable than a switchCanady
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.Spicate
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.Toot
Can be cleaner in some circumstances. Also slower, since it requires function calls.Irv
If your dictionary is using a decently implemented hash table under the hood then performance degradation should be minimal.Toot
That depends on your keys. A compiler can compile a switch statement into either a simple lookup or an extremely fast static binary search, in either case without requiring any function calls.Irv
This is true. I would assume the goal here is readability/maintainability. Massive switch statements can quickly become unwieldy and lend themselves to some junior programmer to shove other stuff in there that doesn't belong. You are correct about compilers though. No need to try and be smarter.Toot
In fact, I have seen compilers which are able to recognize such a mapping pattern and compile it into a switch-statement. :)Fils
@Fils Probably functional language compilers?Curmudgeon
S
14

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.

Spicate answered 24/9, 2008 at 10:35 Comment(1)
Im assuming that was sarcasm :)Underbelly
F
6

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.
Fret answered 24/9, 2008 at 10:51 Comment(0)
J
5

'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.

Junction answered 24/9, 2008 at 10:45 Comment(2)
OK, but why do we need 5 different, redundant ways of doing the same thing - conditional execution?Overage
@mike.amy: Because each method has different benefits and costs and it's all about getting the most benefit with the least cost.Junction
B
5

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 another case 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 a switch 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.

Baelbeer answered 24/9, 2008 at 11:24 Comment(0)
S
4

if-else

I refute the premise that switch is inherently bad though.

Sebrinasebum answered 24/9, 2008 at 10:36 Comment(0)
E
3

Well, for one, I didn't know using switch was an anti pattern.

Secondly, switch can always be replaced with if / else if statements.

Excommunicate answered 24/9, 2008 at 10:36 Comment(1)
exactly - switch is just syntactic methadone for a bunch of if/elsifs.Overage
I
3

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.

Irv answered 24/9, 2008 at 10:38 Comment(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.Overage
F
2

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.

Fluency answered 24/9, 2008 at 12:5 Comment(0)
F
2

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".

Friday answered 25/11, 2008 at 18:11 Comment(0)
M
2

In JavaScript using Associative arrays, this:

function getItemPricing(customer, item) {
    switch (customer.type) {
        // VIPs are awesome. Give them 50% off.
        case 'VIP':
            return item.price * item.quantity * 0.50;

            // Preferred customers are no VIPs, but they still get 25% off.
        case 'Preferred':
            return item.price * item.quantity * 0.75;

            // No discount for other customers.
        case 'Regular':
        case
        default:
            return item.price * item.quantity;
    }
}

becomes this:

function getItemPricing(customer, item) {
var pricing = {
    'VIP': function(item) {
        return item.price * item.quantity * 0.50;
    },
    'Preferred': function(item) {
        if (item.price <= 100.0)
            return item.price * item.quantity * 0.75;

        // Else
        return item.price * item.quantity;
    },
    'Regular': function(item) {
        return item.price * item.quantity;
    }
};

    if (pricing[customer.type])
        return pricing[customer.type](item);
    else
        return pricing.Regular(item);
}

Courtesy

Maillol answered 1/7, 2015 at 8:15 Comment(0)
C
1

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...

Coatee answered 24/9, 2008 at 10:45 Comment(0)
V
1

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.

Verile answered 24/9, 2008 at 11:4 Comment(0)
R
1

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. :)

Rochet answered 24/9, 2008 at 15:37 Comment(0)
T
0

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.

Trochal answered 24/9, 2008 at 10:37 Comment(0)
T
0

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.

Testator answered 24/9, 2008 at 10:49 Comment(0)
C
0

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!

Cribbage answered 24/9, 2008 at 10:56 Comment(0)
A
0

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?

Alarice answered 24/9, 2008 at 12:16 Comment(0)
T
-13

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.

Tawny answered 24/9, 2008 at 10:53 Comment(8)
Wow - obviously a contentious answer. I'd be interested in knowing what I got so wrong.Tawny
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?Lunulate
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")Lunulate
"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.Allyl
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.Tawny
(2) You're right about the incorrect use of Break and "do something". I'll update the examples. (3) Your code may run on a mobile platform, and in this situation, then optimisation is a good thing. But generally, you shouldn't optimise until you have to. Readability comes first.Tawny
I've updated my example to show why I consider Switch to be difficult.Tawny
Welcome to the club of contentious answers. I'm a charter member. C was made by/for hairy OS coders. I suspect they wanted something that would easily generate a jump table, which sometimes is fast. They didn't care too much about "niceness" then.Villarreal

© 2022 - 2024 — McMap. All rights reserved.