63

I am working in a .Net, C# shop and I have a coworker that keeps insisting that we should use giant Switch statements in our code with lots of "Cases" rather than more object oriented approaches. His argument consistently goes back to the fact that a Switch statement compiles to a "cpu jump table" and is therefore the fastest option (even though in other things our team is told that we don't care about speed).

I honestly don't have an argument against this...because I don't know what the heck he's talking about.
Is he right?
Is he just talking out his ass?
Just trying to learn here.

Adam Lear
  • 31,939
  • 8
  • 101
  • 125
James P. Wright
  • 2,135
  • 4
  • 18
  • 25
  • 7
    You can verify if he's right by using something like .NET Reflector to look at the assembly code and look for the "cpu jump table". – FrustratedWithFormsDesigner May 25 '11 at 15:21
  • 6
    "Switch statement compiles to a "cpu jump table" So does worst-case method dispatching with all pure-virtual functions. None virtual functions are simply linked in directly. Have you dumped any code to compare? – S.Lott May 25 '11 at 15:26
  • 72
    Code should be written for PEOPLE not for machines, otherwise we would just do everything in assembly. – maple_shaft May 25 '11 at 16:14
  • 9
    If he's that much of a noodge, quote Knuth to him : "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil." – DaveE May 25 '11 at 16:50
  • See here: http://stackoverflow.com/questions/234458/do-polymorphism-or-conditionals-promote-better-design/234491#234491 Also using polymorphic objects is just as quick as a switch. As it is optimized into a single function call based on the type of object. – Martin York May 25 '11 at 17:01
  • Can someone make this Community Wiki considering I would not know what is the "correct" answer anyway? – James P. Wright May 25 '11 at 17:08
  • @Pselus CW is for posts that'd benefit from collaborative editing. Even CW posts can have a "correct" answer. You can accept the answer that provides the most compelling arguments that change your coworker's mind? :) – Adam Lear May 25 '11 at 18:24
  • I would ask your coworker for the following pieces of evidence: 1) switch statements create a "cpu jump table" and 2) Object-oriented techniques do not. Without anything to back up those claims, he cannot claim to be right. Conversely, find some documentation to prove your point as well. – Jesse C. Slicer May 25 '11 at 18:36
  • 13
    Maintainability. Any other questions with one word answers I can help you with? – Matt Ellen May 25 '11 at 19:34
  • 1
    Ask him to estimate the gain in performance for your application. – Kevin Hsu May 25 '11 at 23:51
  • How many lines is the entire `switch` block? (i.e. average number of lines per case, times number of cases) If more than 1.5 screens (45 lines?) first consider breaking into functions (if there's one or two cases which is big), then evaluate the benefits of OOP. Also, if the exact same `switch (theTypeCode)` happens twice or more, interface + polymorphism will be easier to maintain. (even if it happens only once, delegates and anonymous functions will *sometimes* be more maintainable than switch). I think it's better to just try different ways and compare the two versions of code. – rwong May 26 '11 at 06:12
  • 2
    _a Switch statement compiles to a "cpu jump table"_ You mean like a VTable that is created to select which function is called in a virtual override? – Michael Brown Jul 10 '12 at 22:09
  • 1
    Now I understand what was meant by "Premature optimization is the root of all evil" and "We write code for people not the compiler" – Songo Aug 29 '12 at 08:04
  • @FrustratedWithFormsDesigner: .NET Reflector doesn't show assembly, it shows MSIL. There's a whole optimizing compile stage between the two. – Ben Voigt Aug 12 '14 at 18:38
  • see also: [Clean readable code vs fast hard to read code. When to cross the line?](http://programmers.stackexchange.com/q/89620/31260) – gnat Feb 04 '15 at 19:45
  • his advice only applies in very special cases where this type of optimization is required. you can replace gigantic/often-used loops in long running processes with case statements, and gain SERIOUS performance. I've only come across this situation 3 times in 22 years. otherwise maintainabiity>performance – DaFi4 Sep 29 '16 at 12:55
  • Turn it upside down - require the tests to be written before the code. Then see what design comes out. – Thorbjørn Ravn Andersen Oct 23 '21 at 15:56

17 Answers17

51

He is probably an old C hacker and yes, he talking out of his ass. .Net is not C++; the .Net compiler keeps on getting better and most clever hacks are counter-productive, if not today then in the next .Net version. Small functions are preferable because .Net JIT-s every function once before it is being used. So, if some cases never get hit during a LifeCycle of a program, so no cost is incurred in JIT-compiling these. Anyhow, if speed is not an issue, there should not be optimizations. Write for programmer first, for compiler second. Your co-worker will not be easily convinced, so I would prove empirically that better organized code is actually faster. I would pick one of his worst examples, rewrite them in a better way, and then make sure that your code is faster. Cherry-pick if you must. Then run it a few million times, profile and show him. That ought to teach him well.

EDIT

Bill Wagner wrote:

Item 11: Understand the Attraction of Small Functions(Effective C# Second Edition)   Remember that translating your C# code into machine-executable code is a two-step process. The C# compiler generates IL that gets delivered in assemblies. The JIT compiler generates machine code for each method (or group of methods, when inlining is involved), as needed. Small functions make it much easier for the JIT compiler to amortize that cost. Small functions are also more likely to be candidates for inlining. It’s not just smallness: Simpler control flow matters just as much. Fewer control branches inside functions make it easier for the JIT compiler to enregister variables. It’s not just good practice to write clearer code; it’s how you create more efficient code at runtime.

EDIT2:

So ... apparently a switch statement is faster and better than a bunch of if/else statements, because one comparison is logarithmic and another is linear. http://sequence-points.blogspot.com/2007/10/why-is-switch-statement-faster-than-if.html

Well, my favorite approach to replacing a huge switch statement is with a dictionary (or sometimes even an array if I am switching on enums or small ints) that is mapping values to functions that get called in response to them. Doing so forces one to remove a lot of nasty shared spaghetti state, but that is a good thing. A large switch statement is usually a maintenance nightmare. So ... with arrays and dictionaries, the lookup will take a constant time, and there will be little extra memory wasted.

I am still not convinced that the switch statement is better.

Job
  • 6,459
  • 3
  • 32
  • 54
  • 50
    Don't worry about proving it faster. This is premature optimization. The millisecond you might save is nothing compared to that index you forgot to add to the database that costs you 200ms. You're fighting the wrong battle. – Rein Henrichs May 25 '11 at 15:47
  • 1
    I know that, but humans are no robot. Many cannot be told unless they already know. So, they need a dramatic experience to change their habits. – Job May 25 '11 at 15:56
  • 30
    @Job what if he's actually right? The point isn't that he's wrong, the point is that he's right *and it doesn't matter*. – Rein Henrichs May 25 '11 at 16:00
  • Most software is complex and he cannot be right 100% of the time. There must be cases when he is also wrong - that should make a thinking individual stop preaching, even if they are obsessed with speed. But yes convincing someone that it does not matter would be nice too. Being a relatively junior guy surrounded by seasoned C++ hackers switching to .Net I often see how my arguments are lost on others, so for me to have a chance to prove my point, I have to stick to the guns that they chose and just out-do them. – Job May 25 '11 at 16:06
  • 2
    Even if he were right about 100% of the cases he is still wasting our time. – Jeremy May 25 '11 at 18:32
  • 7
    I want to gouge my eyes out trying to read that page you linked. – AttackingHobo May 25 '11 at 19:25
  • First of all, I agree with Rein that it doesn't matter. Second of all, I think the switch statement is slower than OO, because OO uses a constant time virtual method lookup, compared to the logarithmic switch. But most of all it doesn't matter. – Jaap May 25 '11 at 20:34
  • 1
    The dictionary lookup will be nowhere near as quick as indexing into an array. The latter just involves looking at an offset from a memory address; the former involves producing a hash of the requested key, then inspecting the implied offset from the starting memory address of the dictionary, and then (depending on the implementation of the dictionary) potentially looking up the item elsewhere due to hash collisions. http://stackoverflow.com/questions/908050/optimizing-lookups-dictionary-key-lookups-vs-array-index-lookups However, the difference is too small to worry about. – Ant May 26 '11 at 08:45
  • 2
    Re: "EDIT2" **First**, the question was about OO vs. switch - bunch of it/else doesn't feature. **Second**, switch using a jump table would be O(1) i.e. constant & if/else O(n) i.e. linear - neither is logarithmic. **Third**, switch (if appropriate in the first place) is **both** faster and more readable than if/else - so there's not much to debate about. (However: yes switch is faster. But n is usually too small to make a real difference. Even at 50 options, if _overall_ performance is improved that much, the method is probably called too often.) – Disillusioned Sep 24 '13 at 22:50
  • 7
    What's with the C++ hate? The C++ compilers are getting better too, and big switches are just as bad in C++ as in C#, and for exactly the same reason. If you're surrounded by former C++ programmers who give you grief it's not because they're C++ programmers, it's because they are bad programmers. – Sebastian Redl Feb 06 '14 at 11:15
  • Back in the day I use to write C/C++ for a variety of system. I have been hard core Java for years. I have never written any C# code. What I can add to this conversation is that this isn't just a language specific thing. It's good OO and programming practice to functionally decompose your code. And use polymorphism to handle complexity effectively – Christian Bongiorno Apr 10 '14 at 17:33
  • @Craig: There's no reason for if/else trees to have linear complexity. Oh, the ladder does, but arrange the tests to divide-and-conquer in a balanced tree and you have logarithmic time. Sort by a-priori probability, and you can do even better than that. – Ben Voigt Aug 12 '14 at 18:41
  • Please see the reference in my answer: large switch statements are converted to Dictionaries by the compiler. Your favourite approach makes sense therefore both as that's what the switch statement ends up as, and it's easier to read. – David Arno Jan 05 '16 at 12:35
  • @Job For your EDIT2, I've actually had a situation where I had to replace an old C hacker's giant switch with a dictionary-like array in order to make the code readable/sustainable. – Snoop Apr 19 '16 at 11:23
  • Since the compiler provides the best optimization for you, I think the `switch` statement is best - it is basic coding understandable by any C# programmer, rather than advanced `Dictionary` of delegates or other tricky method pointer logic, providing shared state means you don't have to work hard to get around not having shared state when you need it. – NetMage Feb 08 '19 at 21:26
42

Unless your colleague can provide proof, that this alteration provides an actual measurable benefit on the scale of the whole application, it is inferior to your approach (i.e. polymorphism), which actually does provide such a benefit: maintainability.

Microoptimisation should only be done, after bottlenecks are pinned down. Premature optimization is the root of all evil.

Speed is quantifiable. There's little useful information in "approach A is faster than approach B". The question is "How much faster?".

Jim G.
  • 8,006
  • 3
  • 35
  • 66
back2dos
  • 29,980
  • 3
  • 73
  • 114
  • 2
    Absolutely true. Never *claim* that something is faster, always measure. And measure only when that part of the application is the performance bottleneck. – Kilian Foth May 25 '11 at 15:53
  • 1
    And know how to measure objectively. – Job May 25 '11 at 15:56
  • 7
    -1 for "Premature optimization is the root of all evil." Please display the _entire_ quote, not just one part that biases Knuth's opinion. – alternative May 25 '11 at 19:15
  • 2
    @mathepic: I intentionally did not present this as a quote. This sentence, as is, is my personal opinion, although of course not my creation. Although it may be noted that the guys from c2 seem to consider just that part the core wisdom. – back2dos May 25 '11 at 20:53
  • 10
    @alternative The full Knuth quote "There is no doubt that the grail of efficiency leads to abuse. Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil." Describes the OP's coworker perfectly. IMHO back2dos summarised the quote well with "premature optimisation is the root of all evil" – MarkJ Jul 25 '13 at 10:47
  • 2
    @MarkJ 97% of the time – alternative Jul 25 '13 at 18:20
28

Who cares if it's faster?

Unless you're writing real-time software it's unlikely that the minuscule amount of speedup you might possibly get from doing something in a completely insane manner will make much difference to your client. I wouldn't even go about battling this one on the speed front, this guy is clearly not going to listen to any argument on the subject.

Maintainability, however, is the aim of the game, and a giant switch statement is not even slightly maintainable, how do you explain the different paths through the code to a new guys? The documentation will have to be as long as the code itself!

Plus, you've then got the complete inability to unit test effectively (too many possible paths, not to mention the probable lack of interfaces etc.), which makes your code even less maintainable.

[On the being-interested side: the JITter performs better on smaller methods, so giant switch statements (and their inherently large methods) will harm your speed in large assemblies, IIRC.]

Ed James
  • 3,489
  • 3
  • 22
  • 33
  • 1
    + a huge example of premature optimization. – ShaneC May 25 '11 at 16:26
  • Definitely this. – DeadMG May 25 '11 at 18:58
  • +1 for 'a giant switch statement is not even slightly maintainable' – Korey Hinton Jul 25 '13 at 14:14
  • 3
    A giant switch statement is a lot easier for the new guy to comprehend: all the possible behaviors are collected right there in a nice neat list. Indirect calls are extremely difficult to follow, in the worst case (function pointer) you need to search the entire code base for functions of the right signature, and virtual calls are only a little better (search for functions of the right name and signature and related by inheritance). But maintainability is not about being read-only. – Ben Voigt Aug 12 '14 at 18:44
  • Sentence #1: “Beginners save nanoseconds. People who are good at it save milliseconds.” – gnasher729 Oct 23 '21 at 14:45
13

Step away from the switch statement ...

This type of switch statement should be shunned like a plague because it violates the Open Closed Principle. It forces the team to make changes to existing code when new functionality needs to be added, as opposed to, just adding new code.

phm271
  • 3
  • 3
Dakotah North
  • 3,353
  • 1
  • 20
  • 31
  • 12
    That comes with a caveat. There are operations (functions/methods) and types. When you add a new operation, you only have to change the code in one place for the switch statements (add one new function with switch statement), but you'd have to add that method to all classes in the OO case (violates open/closed principle). If you add new types, you have to touch every switch statement, but in the OO case you'd just add one more class. Therefore to make an informed decision you have to know if you'll be adding more operations to the existing types, or adding more types. – Scott Whitlock May 25 '11 at 15:34
  • 4
    If you need to add more operations to existing types in an OO paradigm without violating OCP, then I believe that's what the visitor pattern is for. – Scott Whitlock May 25 '11 at 15:37
  • @Scott Whitlock: Poppycock. With the OO approach you would never need to violate the Open/Close principle (in this situation (if you are then you have designed the underlying objects incorrectly)). In the situation you describe the switch statement needs to be modified and and a new function added, the OO version a new class needs to be defined that implements the `Action()` method. If you need more complex refactoring then both switch and OO will be affected in a complex manor but the compiler will catch any problems in OO while the switch will happily blow up at ruin-time. – Martin York May 25 '11 at 17:16
  • 3
    @Martin - name call if you will, but this is a well-known tradeoff. I refer you to Clean Code by R.C. Martin. He revisits his article on OCP, explaining what I outlined above. You can't simultaneously design for all future requirements. You have to make a choice between whether it's more likely to add more operations or more types. OO favours the addition of types. You can use OO to add more operations if you model operations as classes, but that seems to get into the visitor pattern, which has its own issues (notably overhead). – Scott Whitlock May 25 '11 at 18:14
  • @Scott Whitlock: Sorry about poppycock :-). But you are confusing two different situations. Situation 1: as described above where you have a switch and just add tasks. Which will not involve any changes to OO. Or Situation 2: where you need to add more functionality which requires changes to OO and re-factoring and copying the switch. See my answer and explain to me how the open/close principle needs to ever be broken where there is not a significantly larger change in the switch. – Martin York May 25 '11 at 18:45
  • 8
    @Martin: Have you ever written a parser? It's quite common to have large switch-cases that switch on the next token in the lookahead buffer. You *could* replace those switches with virtual function calls to the next token, but that would be a maintainence nightmare. It's rare, but sometimes the switch-case is actually the better choice, because it keeps code that should be read/modified together in close proximity. – nikie May 25 '11 at 18:53
  • @nikie: Yes its easy to make situations where one technique is better than the other (in both directions). But we are talking about the general case. Usually the OO technique works better for readability/maintenance. Having written C and C++ compilers I consider the parser example you use is a terrible (not just for these but all parsers I build). The switch statement in this context is generally generated from a higher level language and thus neither maintained or readability nor a real factor as it is the higher level language that you check in and maintain. – Martin York May 25 '11 at 20:28
  • 1
    @Martin: You used words like "never", "ever" and "Poppycock", so I was assuming you're talking about all cases without exceptions, not just about the most common case. (And BTW: People still write parsers by hand. For example, the CPython parser is still written by hand, IIRC.) – nikie May 26 '11 at 09:05
  • @nikie: Lets see context: "never ... in this situation". "ever .... when there is not". OK. Poppycock you have me maybe I should have used balderdash. Yes people write parsers by hand. I just expressed it was a bad example as normally they don't and when they do is switch the control structure! – Martin York May 26 '11 at 13:59
  • 1
    @LokiAstari The newest and fastest-growing C++ compiler, Clang, uses lots and lots of switch statements in its hand-written parser. The compiler team actually added very powerful switch completeness warnings to the compiler so that you can easily track down all switch statements that need to be modified. Yes, it's not always easy for maintenance, but that's just the nature of the problem. In fact, the problem is so common that it has a name: expression problem. – Sebastian Redl Jan 05 '16 at 12:51
  • Somehow newer, Swift has enum that are objects. With code. Lots of the code is typically switch statements :-) – gnasher729 Oct 23 '21 at 14:50
11

I don't buy the performance argument; it's all about code maintainability.

BUT: sometimes, a giant switch statement is easier to maintain (less code) than a bunch of small classes overriding virtual function(s) of an abstract base class. For example, if you were to implement a CPU emulator, you would not implement the functionality of each instruction in a separate class -- you would just stuff it into a giant swtich on the opcode, possibly calling helper functions for more complex instructions.

Rule of thumb: if the switch is somehow performed on the TYPE, you should probably use inheritance and virtual functions. If the switch is performed on a VALUE of a fixed type (e.g., the instruction opcode, as above), it's OK to leave it as it is.

zvrba
  • 3,470
  • 2
  • 23
  • 22
8

I have survived the nightmare known as the massive finite state machine manipulated by massive switch statements. Even worse, in my case, the FSM spanned three C++ DLLs and it was quite plain the code was written by someone versed in C.

The metrics you need to care about are:

  • Speed of making a change
  • Speed of finding the problem when it happens

I was given the task of adding a new feature to that set of DLLs, and was able to convince management that it would take me just as long to rewrite the 3 DLLs as one properly object oriented DLL as it would be for me to monkey patch and jury rig the solution into what was already there. The rewrite was a huge success, as it not only supported the new functionality but was much easier to extend. In fact, a task that would normally take a week to make sure you didn't break anything would end up taking a few hours.

So how about execution times? There was no speed increase or decrease. To be fair our performance was throttled by the system drivers, so if the object oriented solution was in fact slower we wouldn't know it.

What's wrong with massive switch statements for an OO language?

  • Program control flow is taken away from the object where it belongs and placed outside the object
  • Many points of external control translates into many places you need to review
  • It is unclear where state is stored, particularly if the switch is inside a loop
  • The quickest comparison is no comparison at all (you can avoid the need for many comparisons with a good object oriented design)
  • It's more efficient to iterate through your objects and always call the same method on all of the objects than it is to change your code based on the object type or enum that encodes the type.
Berin Loritsch
  • 45,784
  • 7
  • 87
  • 160
5

He is correct that the resulting machine code will probably be more efficient. The compiler essential transforms a switch statement into a set of tests and branches, which will be relatively few instructions. There is a high chance that the code resulting from more abstracted approaches will require more instructions.

HOWEVER: It's almost certainly the case that your particular application doesn't need to worry about this kind of micro-optimisation, or you wouldn't be using .net in the first place. For anything short of very constrained embedded applications, or CPU intensive work you should always let the compiler deal with optimisation. Concentrate on writing clean, maintainable code. This is almost always of far great value than a few tenths of a nano-second in execution time.

Luke Graham
  • 2,393
  • 18
  • 20
5

You can't convince me that:

void action1()
{}

void action2()
{}

void action3()
{}

void action4()
{}

void doAction(int action)
{
    switch(action)
    {
        case 1: action1();break;
        case 2: action2();break;
        case 3: action3();break;
        case 4: action4();break;
    }
}

Is significantly faster than:

struct IAction
{
    virtual ~IAction() {}
    virtual void action() = 0;
}

struct Action1: public IAction
{
    virtual void action()    { }
}

struct Action2: public IAction
{
    virtual void action()    { }
}

struct Action3: public IAction
{
    virtual void action()    { }
}

struct Action4: public IAction
{
    virtual void action()    { }
}

void doAction(IAction& actionObject)
{
    actionObject.action();
}

Additionally the OO version is just more maintainable.

Martin York
  • 11,150
  • 2
  • 42
  • 70
  • 9
    For some things and for smaller amounts of actions, the OO version is much goofier. It has to have some kind of factory to convert some value into the creation of an IAction. In many cases it's a lot more readable to just switch on that value instead. – Zan Lynx May 25 '11 at 19:49
  • @Zan Lynx: Your argument is too generic. The creation of the IAction object is just as difficult as retrieving the action integer no harder no easier. So we can have a real conversation without be way to generic. Consider a calculator. Whats the difference in complexity here? The answer is zero. As all actions pre-created. You get the input from the user and its already an action. – Martin York May 25 '11 at 20:33
  • 3
    @Martin: You are assuming a GUI calculator app. Let's take a keyboard calculator app written for C++ on an embedded system instead. Now you have a scan-code integer from a hardware register. Now what is less complex? – Zan Lynx May 25 '11 at 21:15
  • @Zan Lynx: I was not assuming any interface (don't like users life would be a lot simpler without them) I still don't see where your example makes it more complex! – Martin York May 26 '11 at 14:00
  • 3
    @Martin: You don't see how integer -> lookup table -> creation of new object -> call virtual function is more complicated than integer -> switch -> function? How do you not see that? – Zan Lynx May 26 '11 at 16:30
  • @Zan: I don't see the need for `lookup table -> creation of new object`. Maybe you should write the program using switch I will write one using polymorphism – Martin York May 26 '11 at 17:38
  • 2
    @Martin: Maybe I will. In the meantime, explain how you get the IAction object to call action() on from an integer *without* a lookup table. – Zan Lynx May 26 '11 at 18:08
  • I am going to use a lookup table. I am just not going to create a new object. – Martin York May 26 '11 at 20:52
  • Create a hashtable that stores the objects. It's the strategy pattern at its finest. – Michael Brown Jul 10 '12 at 22:15
  • @MikeBrown: Wrong Pattern. All he needs is a virtual method. The OP is trying to reinvent virtual dispatch using a switch. – Martin York Jul 11 '12 at 00:15
4

Normally I hate the word, "premature optimization", but this reeks of it. It's worth noting that Knuth used this famous quote in the context of pushing to use goto statements in order to speed up code in critical areas. That's the key: critical paths.

He was suggesting to use goto for speeding up code but warning against those programmers who would want to do these types of things based on hunches and superstitions for code that isn't even critical.

To favor switch statements as much as possible uniformly throughout a codebase (whether or not any heavy load is handled) is the classic example of what Knuth calls the "penny-wise and pound-foolish" programmer who spends all day struggling to maintain their "optimized" code which turned into a debugging nightmare as a result of trying to save pennies over pounds. Such code is rarely maintainable let alone even efficient in the first place.

Is he right?

He is correct from the very basic efficiency perspective. No compiler to my knowledge can optimize polymorphic code involving objects and dynamic dispatch better than a switch statement. You'll never end up with a LUT or jump table to inlined code from polymorphic code, since such code tends to serve as an optimizer barrier for the compiler (it won't know which function to call until the time at which the dynamic dispatch occurs).

It's more useful not to think of this cost in terms of jump tables but more in terms of the optimization barrier. For polymorphism, calling Base.method() doesn't allow the compiler to know which function will actually end up being called if method is virtual, not sealed, and can be overridden. Since it doesn't know which function is actually going to be called in advance, it can't optimize away the function call and utilize more information in making optimization decisions, since it doesn't actually know which function is going to be called at the time the code is being compiled.

Optimizers are at their best when they can peer into a function call and make optimizations that either completely flatten the caller and callee, or at least optimize the caller to most efficiently work with the callee. They can't do that if they don't know which function is actually going to be called in advance.

Is he just talking out his ass?

Using this cost, which often amounts to pennies, to justify turning this into a coding standard applied uniformly is generally very foolish, especially for places that have an extensibility need. That's the main thing you want to watch out for with genuine premature optimizers: they want to turn minor performance concerns into coding standards applied uniformly throughout a codebase with no regard for maintainability whatsoever.

I take a little offense to the "old C hacker" quote used in the accepted answer though, since I'm one of those. Not everyone who has been coding for decades starting from very limited hardware has turned into a premature optimizer. Yet I've encountered and worked with those too. But those types never measure things like branch misprediction or cache misses, they think they know better, and base their notions of inefficiency in a complex production codebase based on superstitions which don't hold true today and sometimes never held true. People who have genuinely worked in performance-critical fields often understand that effective optimization is effective prioritization, and trying to generalize a maintainability-degrading coding standard out to save pennies is very ineffective prioritization.

Pennies are important when you have a cheap function that doesn't do so much work which is called a billion times in a very tight, performance-critical loop. In that case, we end up saving 10 million dollars. It's not worth shaving pennies when you have a function called two times for which the body alone costs thousands of dollars. It's not wise to spend your time haggling over pennies during the purchase of a car. It is worth haggling over pennies if you are purchasing a million cans of soda from a manufacturer. The key to effective optimization is to understand these costs in their proper context. Someone who tries to save pennies on every single purchase and suggests that everyone else tries to haggle over pennies no matter what they're purchasing isn't a skilled optimizer.

3

One major reason to use classes instead of switch statements is that switch statements tends to lead to one huge file that have lots of logic. This is both a maintainance nightmare as well as a problem with source management since you have to check out and edit that huge file instead of a different smaller class files

Homde
  • 11,104
  • 3
  • 40
  • 68
3

a switch statement in OOP code is a strong indiciation of missing classes

try it both ways and run some simple speed tests; chances are the difference are not significant. If they are and the code is time-critical then keep the switch statement

Steven A. Lowe
  • 33,808
  • 2
  • 84
  • 151
2

One maintainability advantage of polymorphism that no-one has mentioned is that you will be able to structure your code much more nicely using inheritance if you are always switching on the same list of cases, but sometime several cases are handled the same way and sometime they aren't

Eg. if you are switching between Dog, Cat and Elephant, and sometimes Dog and Cat have the same case, you can make them both inherit from an abstract class DomesticAnimal and put those function in the abstract class.

Also, I was surprised that several people used a parser as an example of where you wouldn't use polymorphism. For a tree-like parser this is definitely the wrong approach, but if you have something like assembly, where each line is somewhat independent, and start with an opcode that indicates how the rest of the line should be interpreted, I would totally use polymorphism and a Factory. Each class can implement functions like ExtractConstants or ExtractSymbols. I have used this approach for a toy BASIC interpreter.

jwg
  • 214
  • 1
  • 11
  • A switch can inherit behaviors too, through it's default case. "... extends BaseOperationVisitor" becomes "default: BaseOperation (node)" – Samuel Danielson Mar 11 '17 at 08:12
2

It sounds like your coworker is very concerned about performance. It might be that in some cases a large case/switch structure will perform faster, but hopefully you guys would do an experiment by doing timing tests on the OO version and the switch/case version. I am guessing the OO version has less code and is easier to follow, understand and maintain. I would argue for the OO version first (as maintenance/readability should be initially more important), and only consider the switch/case version only if the OO version has serious performance issues and it can be shown that a switch/case will make a significant improvement.

FrustratedWithFormsDesigner
  • 46,105
  • 7
  • 126
  • 176
  • 1
    Along with timing tests, a code dump can help show how C++ (and C#) method dispatching works. – S.Lott May 25 '11 at 15:27
  • I have this suspicion that he fears the “object oriented” approach - which is actually a “lots of little classes” - will be a disaster and he came up with an excuse. I personally think five huge switch statements with 100 cases are a lot more readable than 100 classes with five methods each. – gnasher729 Oct 23 '21 at 15:05
1

Your colleague is not talking out of his backside, as far as the comment regarding jump tables goes. However, using that to justify writing bad code is where he goes wrong.

The C# compiler converts switch statements with just a few cases into a series of if/else's, so is no faster than using if/else. The compiler converts larger switch statements into a Dictionary (the jump table that your colleague is referring to). Please see this answer to a Stack Overflow question on the topic for more details.

A large switch statement is hard to read and maintain. A dictionary of "cases" and functions is a lot easier to read. As that is what the switch gets turned into, you and your colleague would be well advised to use dictionaries directly.

David Arno
  • 38,972
  • 9
  • 88
  • 121
  • 100 little classes are harder to read and maintain. And explain to me what makes a large switch statement hard to maintain and read: it has a much better ratio of overhead to implementation code. – gnasher729 Oct 23 '21 at 15:17
1

He's not necessarily talking out of his ass. At least in C and C++ switch statements can be optimized to jump tables while I've never seen it happen with a dynamic dispatch in a function that only has access to a base pointer. At the very least the latter requires a much smarter optimizer looking at much more surrounding code to figure out exactly what subtype is being used from a virtual function call through a base pointer/reference.

On top of that the dynamic dispatch often serves as an "optimization barrier", meaning the compiler often won't be able to inline code and optimally allocate registers to minimize stack spills and all that fancy stuff, since it can't figure out what virtual function is going to be called through the base pointer to inline it and do all of its optimization magic. I'm not sure you even want the optimizer to be so smart and try to optimize away indirect function calls, since that could potentially lead to many branches of code having to be generated separately down a given call stack (a function that calls foo->f() would have to generate totally different machine code from one that calls bar->f() through a base pointer, and the function that calls that function would then have to generate two or more versions of code, and so forth -- the amount of machine code being generated would be explosive -- maybe not so bad with a trace JIT which generates the code on the fly as it's tracing through hot execution paths).

However, as many answers have echoed, that's a bad reason to favor a boatload of switch statements even if it's hands-down faster by some marginal amount. Besides, when it comes to micro-efficiencies, things like branching and inlining are usually pretty low priority compared to things like memory access patterns.

That said, I jumped in here with an unusual answer. I want to make a case for the maintainability of switch statements over a polymorphic solution when, and only when, you know for sure that there is only going to be one place that needs to perform the switch.

A prime example is a central event handler. In that case you generally don't have many places handling events, just one (why it's "central"). For those cases, you don't benefit from the extensibility that a polymorphic solution provides. A polymorphic solution is beneficial when there are many places that would do the analogical switch statement. If you know for sure there's only going to be one, a switch statement with 15 cases can be a whole lot simpler than designing a base class inherited by 15 subtypes with overridden functions and a factory to instantiate them, only to then be used in one function in the entire system. In those cases, adding a new subtype is a lot more tedious than adding a case statement to one function. If anything, I'd argue for the maintainability, not the performance, of switch statements in this one peculiar case where you don't benefit from extensibility whatsoever.

0

Even if this wasn't bad for maintainability, I don't believe that it will be better for performance. A virtual function call is simply one extra indirection (the same as the best case for a switch statement) so even in C++ the performance should be roughly equal. In C#, where all function calls are virtual, the switch statement should be worse, since you have the same virtual function call overhead in both versions.

Dirk Holsopple
  • 2,701
  • 18
  • 16
0

"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil"

Donald Knuth

thorsten müller
  • 12,058
  • 4
  • 49
  • 54
  • True but irrelevant. Breaking perfectly fine code in order to follow some principle would annoy him much more. – gnasher729 Oct 23 '21 at 15:18