41

I recently stumbled across this entry in the google testing blog about guidelines for writing more testable code. I was in agreement with the author until this point:

Favor polymorphism over conditionals: If you see a switch statement you should think polymorphisms. If you see the same if condition repeated in many places in your class you should again think polymorphism. Polymorphism will break your complex class into several smaller simpler classes which clearly define which pieces of the code are related and execute together. This helps testing since simpler/smaller class is easier to test.

I simply cannot wrap my head around that. I can understand using polymorphism instead of RTTI (or DIY-RTTI, as the case may be), but that seems like such a broad statement that I can't imagine it actually being used effectively in production code. It seems to me, rather, that it would be easier to add additional test cases for methods which have switch statements, rather than breaking down the code into dozens of separate classes.

Also, I was under the impression that polymorphism can lead to all sorts of other subtle bugs and design issues, so I'm curious to know if the tradeoff here would be worth it. Can someone explain to me exactly what is meant by this testing guideline?

Nik Reiman
  • 39,067
  • 29
  • 104
  • 160

12 Answers12

73

Actually this makes testing and code easier to write.

If you have one switch statement based on an internal field you probably have the same switch in multiple places doing slightly different things. This causes problems when you add a new case as you have to update all the switch statements (if you can find them).

By using polymorphism you can use virtual functions to get the same functionality and because a new case is a new class you don't have to search your code for things that need to be checked it is all isolated for each class.

class Animal
{
    public:
       Noise warningNoise();
       Noise pleasureNoise();
    private:
       AnimalType type;
};

Noise Animal::warningNoise()
{
    switch(type)
    {
        case Cat: return Hiss;
        case Dog: return Bark;
    }
}
Noise Animal::pleasureNoise()
{
    switch(type)
    {
        case Cat: return Purr;
        case Dog: return Bark;
    }
}

In this simple case every new animal causes requires both switch statements to be updated.
You forget one? What is the default? BANG!!

Using polymorphism

class Animal
{
    public:
       virtual Noise warningNoise() = 0;
       virtual Noise pleasureNoise() = 0;
};

class Cat: public Animal
{
   // Compiler forces you to define both method.
   // Otherwise you can't have a Cat object

   // All code local to the cat belongs to the cat.

};

By using polymorphism you can test the Animal class.
Then test each of the derived classes separately.

Also this allows you to ship the Animal class (Closed for alteration) as part of you binary library. But people can still add new Animals (Open for extension) by deriving new classes derived from the Animal header. If all this functionality had been captured inside the Animal class then all animals need to be defined before shipping (Closed/Closed).

Martin York
  • 257,169
  • 86
  • 333
  • 562
  • 1
    Great example +1. Couple of typos to fix though: waningNoise and anamal – Ken Oct 24 '08 at 18:00
  • 9
    Pleasure Noise indeeed! I have a new saying. – Kieveli Jan 06 '09 at 23:25
  • Makes testing and writing easier, ok. What about reading and following the control flow? It will be harder. – Calmarius Dec 14 '12 at 14:50
  • 3
    @Calmarius: Don't think I agree with that. As you move all code that is common to a particular class to a single class. So it modularized the code (which makes in my opinion reading easier). While in the old switch technique the characteristics of how an entity works is spread throughout the code base. This makes it harder to see how a change to a behavior will affect other properties of the entity. – Martin York Dec 14 '12 at 22:14
  • @LokiAstari You often need to make changes in large unknown codebases (aka. maintaining code). The problem is that the control disappears in virtual functions. If you look up the declaration of the function, you only find an abstract class, which is not too helpful. Now if you want to know what can happen you will need to find the derived classes. This becomes very difficult if you have many abstraction layers stack on each other. You only chance is using a debugger and stepping through the code. Which may take hours/days in a code which jumps around source files very wildly. – Calmarius Mar 13 '13 at 12:37
  • 1
    @Calmarius: Or use a real editor that allows you to jump to all definitions of a virtual function in a few key strokes. Which takes seconds (I use vim). Given an old code base to maintain I prefer one structured around virtual calls rather than switches exactly because I think it is easier to maintain. So again I disagree. But that is fine we are allowed to disagree. – Martin York Mar 13 '13 at 15:28
  • @Calmarius I understand your concern having seen it often myself. However, it's not polymorphism that's the problem - it's a **bad OO design**. You mention "_many abstraction layers that stack on each other_". The problem is people get obsessed with code-reuse via inheritance. So they say: lots of animals bark, let's create an `AnimalsThatBark` class. What about `AnimalsWithTails`? Before long, the hierarchy is so convoluted that the only way to get a feature to all appropriate subclasses is bubbling it up the ancestors and switching them with conditionals! – Disillusioned Sep 24 '13 at 08:22
  • @Calmarius continuing from above... A good OO design favours composition over inheritance. This is because: to understand a class, you need to understand its ancestors as well (the fewer the better). Also, changes to an ancestor high up in a class hierarchy can impact a large number of other classes. – Disillusioned Sep 24 '13 at 08:33
  • @Calmarius An actual example of how things can go wrong: We have a template pattern class (A) to guide DB interaction and a subclass (B) which is a "more _advanced_" template with extra features. The problem is that (B) changes things so that subclasses of (B) cannot be used like subclasses of (A). (B) is effectively _breaking_ the ***is-a*** relationship. In this case (B) should **not** be a subclass but rather a _sibling_ of (A)! – Disillusioned Sep 24 '13 at 08:36
  • @CraigYoung I mostly a procedural programmer (I maintain C code). The problems you mentioned simply does not exist in C. No inheritance, no virtual functions. Polymorphism inside a library is done by switches or arrays of function pointers. On the interface it's done by letting the user give us their function pointers and we call back to his code. This ensures that the library is monolithic with single external interface, functions are not forced into various classes in code (they might be in the documentation or in your head if you want). – Calmarius Sep 24 '13 at 10:16
  • 1
    @Calmarius You questioned the wisdom of polymorphism remember: "_The problem is that the control disappears in virtual functions_" I pointed out: polymorphism isn't the problem, but rather bad design. Now you say you have little experience in OO because you maintain C code. Bad design in a procedural tool is just as _bad_ as bad design in an OO tool (perhaps more so when attempting to mimic OO without compiler support). I repeat: it's not polymorphism that's the problem: it's confusing abstractions, monolithic interfaces, side-effects... Perhaps your skepticism is really _fear of the unknown_? – Disillusioned Sep 24 '13 at 11:45
  • 1
    @CraigYoung I didn't say I have only a little experience in OO. I had to work with OO and procedural code as well. Otherwise I wouldn't see the difference. After maintaining OO code (at the previous company), I found maintaining procedural code much easier. Procedural code generally contains less indirections but longer functions. I found it easier to read. – Calmarius Sep 24 '13 at 13:48
  • 1
    @Calmarius Don't you think it's wonderful to have a discussion with someone who: **first** makes _incorrect_ generalisations about a particular technology, **then** exlpains their perspective by pointing out they predominantly use a different technology (`"I mostly a procedural programmer" (sic)`) and **finally** denies saying they have "little experience in OO". You now say you're comparing **one** company's OO code with **one** company's procedural code. In my book that's **very** _limited experience_. Maybe job 2 is easier because you now have a little more experience? – Disillusioned Sep 24 '13 at 18:21
  • @CraigYoung Ok I give up... :) – Calmarius Sep 24 '13 at 18:25
  • @Calmarius I guess we can agree on that. ;) – Disillusioned Sep 24 '13 at 23:32
26

Do not fear...

I guess your problem lies with familiarity, not technology. Familiarize yourself with C++ OOP.

C++ is an OOP language

Among its multiple paradigms, it has OOP features and is more than able to support comparison with most pure OO language.

Don't let the "C part inside C++" make you believe C++ can't deal with other paradigms. C++ can handle a lot of programming paradigms quite graciously. And among them, OOP C++ is the most mature of C++ paradigms after procedural paradigm (i.e. the aforementioned "C part").

Polymorphism is Ok for production

There is no "subtle bugs" or "not suitable for production code" thing. There are developers who remain set in their ways, and developers who'll learn how to use tools and use the best tools for each task.

switch and polymorphism are [almost] similar...

... But polymorphism removed most errors.

The difference is that you must handle the switches manually, whereas polymorphism is more natural, once you get used with inheritance method overriding.

With switches, you'll have to compare a type variable with different types, and handle the differences. With polymorphism, the variable itself knows how to behave. You only have to organize the variables in logical ways, and override the right methods.

But in the end, if you forget to handle a case in switch, the compiler won't tell you, whereas you'll be told if you derive from a class without overriding its pure virtual methods. Thus most switch-errors are avoided.

All in all, the two features are about making choices. But Polymorphism enable you to make more complex and in the same time more natural and thus easier choices.

Avoid using RTTI to find an object's type

RTTI is an interesting concept, and can be useful. But most of the time (i.e. 95% of the time), method overriding and inheritance will be more than enough, and most of your code should not even know the exact type of the object handled, but trust it to do the right thing.

If you use RTTI as a glorified switch, you're missing the point.

(Disclaimer: I am a great fan of the RTTI concept and of dynamic_casts. But one must use the right tool for the task at hand, and most of the time RTTI is used as a glorified switch, which is wrong)

Compare dynamic vs. static polymorphism

If your code does not know the exact type of an object at compile time, then use dynamic polymorphism (i.e. classic inheritance, virtual methods overriding, etc.)

If your code knows the type at compile time, then perhaps you could use static polymorphism, i.e. the CRTP pattern http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern

The CRTP will enable you to have code that smells like dynamic polymorphism, but whose every method call will be resolved statically, which is ideal for some very critical code.

Production code example

A code similar to this one (from memory) is used on production.

The easier solution revolved around a the procedure called by message loop (a WinProc in Win32, but I wrote a simplier version, for simplicity's sake). So summarize, it was something like:

void MyProcedure(int p_iCommand, void *p_vParam)
{
   // A LOT OF CODE ???
   // each case has a lot of code, with both similarities
   // and differences, and of course, casting p_vParam
   // into something, depending on hoping no one
   // did a mistake, associating the wrong command with
   // the wrong data type in p_vParam

   switch(p_iCommand)
   {
      case COMMAND_AAA: { /* A LOT OF CODE (see above) */ } break ;
      case COMMAND_BBB: { /* A LOT OF CODE (see above) */ } break ;
      // etc.
      case COMMAND_XXX: { /* A LOT OF CODE (see above) */ } break ;
      case COMMAND_ZZZ: { /* A LOT OF CODE (see above) */ } break ;
      default: { /* call default procedure */} break ;
   }
}

Each addition of command added a case.

The problem is that some commands where similar, and shared partly their implementation.

So mixing the cases was a risk for evolution.

I resolved the problem by using the Command pattern, that is, creating a base Command object, with one process() method.

So I re-wrote the message procedure, minimizing the dangerous code (i.e. playing with void *, etc.) to a minimum, and wrote it to be sure I would never need to touch it again:

void MyProcedure(int p_iCommand, void *p_vParam)
{
   switch(p_iCommand)
   {
      // Only one case. Isn't it cool?
      case COMMAND:
         {
           Command * c = static_cast<Command *>(p_vParam) ;
           c->process() ;
         }
         break ;
      default: { /* call default procedure */} break ;
   }
}

And then, for each possible command, instead of adding code in the procedure, and mixing (or worse, copy/pasting) the code from similar commands, I created a new command, and derived it either from the Command object, or one of its derived objects:

This led to the hierarchy (represented as a tree):

[+] Command
 |
 +--[+] CommandServer
 |   |
 |   +--[+] CommandServerInitialize
 |   |
 |   +--[+] CommandServerInsert
 |   |
 |   +--[+] CommandServerUpdate
 |   |
 |   +--[+] CommandServerDelete
 |
 +--[+] CommandAction
 |   |
 |   +--[+] CommandActionStart
 |   |
 |   +--[+] CommandActionPause
 |   |
 |   +--[+] CommandActionEnd
 |
 +--[+] CommandMessage

Now, all I needed to do was to override process for each object.

Simple, and easy to extend.

For example, say the CommandAction was supposed to do its process in three phases: "before", "while" and "after". Its code would be something like:

class CommandAction : public Command
{
   // etc.
   virtual void process() // overriding Command::process pure virtual method
   {
      this->processBefore() ;
      this->processWhile() ;
      this->processAfter() ;
   }

   virtual void processBefore() = 0 ; // To be overriden
   
   virtual void processWhile()
   {
      // Do something common for all CommandAction objects
   }
   
   virtual void processAfter()  = 0 ; // To be overriden

} ;

And, for example, CommandActionStart could be coded as:

class CommandActionStart : public CommandAction
{
   // etc.
   virtual void processBefore()
   {
      // Do something common for all CommandActionStart objects
   }

   virtual void processAfter()
   {
      // Do something common for all CommandActionStart objects
   }
} ;

As I said: Easy to understand (if commented properly), and very easy to extend.

The switch is reduced to its bare minimum (i.e. if-like, because we still needed to delegate Windows commands to Windows default procedure), and no need for RTTI (or worse, in-house RTTI).

The same code inside a switch would be quite amusing, I guess (if only judging by the amount of "historical" code I saw in our app at work).

Community
  • 1
  • 1
paercebal
  • 81,378
  • 38
  • 130
  • 159
  • 1
    Very thorough writeup. As a minor niggle, polymorphism in C++ *can* introduce subtle bugs -- in particular, (1) forgetting to declare methods virtual and (2) object slicing, which occurs when you treat a reference to a base class as a value object. Though IMHO the pros outweigh the cons. – j_random_hacker Feb 27 '09 at 11:53
  • @j_random_hacker : You're right in your points (+1, by the way), but the bugs are not in compilers' implementation, but instead in the coder's unfamiliarity with C++. – paercebal Jul 29 '09 at 23:25
  • @Gustavo - Gtoknu : `C++ isn't a subset of C` : Yeah, I believe I knew that... :-P ... Actually, I wrote `Don't let C++'s subset of C`, which I admit is not really clear (see the difference between "AAA's subset" and "AAA subset" ?). I'll clarify the text to make sure no one understand it the wrong way. – paercebal Apr 30 '12 at 12:06
  • override fixes these issues in c++11 – paulm Oct 15 '14 at 14:50
10

Unit testing an OO program means testing each class as a unit. A principle that you want to learn is "Open to extension, closed to modification". I got that from Head First Design Patterns. But it basically says that you want to have the ability to easily extend your code without modifying existing tested code.

Polymorphism makes this possible by eliminating those conditional statements. Consider this example:

Suppose you have a Character object that carries a Weapon. You can write an attack method like this:

If (weapon is a rifle) then //Code to attack with rifle else
If (weapon is a plasma gun) //Then code to attack with plasma gun

etc.

With polymorphism the Character does not have to "know" the type of weapon, simply

weapon.attack()

would work. What happens if a new weapon was invented? Without polymorphism you will have to modify your conditional statement. With polymorphism you will have to add a new class and leave the tested Character class alone.

Vincent Ramdhanie
  • 102,349
  • 23
  • 137
  • 192
  • 1
    Head First Design Patterns is **really** a good book. It is on Jeff Atwood's bookshelf, too! Refer to http://www.codinghorror.com/blog/archives/001108.html – pestophagous Oct 24 '08 at 21:10
8

I'm a bit of a skeptic: I believe inheritance often adds more complexity than it removes.

I think you are asking a good question, though, and one thing I consider is this:

Are you splitting into multiple classes because you are dealing with different things? Or is it really the same thing, acting in a different way?

If it's really a new type, then go ahead and create a new class. But if it's just an option, I generally keep it in the same class.

I believe the default solution is the single-class one, and the onus is on the programmer proposing inheritance to prove their case.

rice
  • 1,063
  • 6
  • 17
  • 1
    If it's the same thing acting in different ways. I would make the action polymorphic not the thing. – Martin York Oct 24 '08 at 18:18
  • 5
    If the blocks of action were different enough, big enough, and numerous enough, then I would support creating new classes for them. But I would rather have one file with one method that has a switch with 75 one-line cases, than 75 classes with 1 line of functionality. – rice Oct 24 '08 at 19:15
  • Rice, I quite agree. I now favour writing units tests (that test functionality) and then writing the simplest possible code to make the tests pass. If I'm adhering to a book written 20 years ago does not really bother me. Don't get me wrong. GoF is great if used right though. – Oliver Shaw Nov 18 '15 at 19:26
5

Not an expert in the implications for test cases, but from a software development perspective:

  • Open-closed principle -- Classes should be closed to alteration, but open to extension. If you manage conditional operations via a conditional construct, then if a new condition is added, your class needs to change. If you use polymorphism, the base class need not change.

  • Don't repeat yourself -- An important part of the guideline is the "same if condition." That indicates that your class has some distinct modes of operation that can be factored into a class. Then, that condition appears in one place in your code -- when you instantiate the object for that mode. And again, if a new one comes along, you only need to change one piece of code.

JohnMcG
  • 8,709
  • 6
  • 42
  • 49
2

Switches and polymorphism does the same thing.

In polymorphism (and in class-based programming in general) you group the functions by their type. When using switches you group the types by function. Decide which view is good for you.

So if your interface is fixed and you only add new types, polymorphism is your friend. But if you add new functions to your interface you will need to update all implementations.

In certain cases, you may have a fixed amount of types, and new functions can come, then switches are better. But adding new types makes you update every switch.

With switches you are duplicating sub-type lists. With polymorphism you are duplicating operation lists. You traded a problem to get a different one. This is the so called expression problem, which is not solved by any programming paradigm I know. The root of the problem is the one-dimensional nature of the text used to represent the code.

Since pro-polymorphism points are well discussed here, let me provide a pro-switch point.

OOP has design patterns to avoid common pitfalls. Procedural programming has design patterns too (but no one have wrote it down yet AFAIK, we need another new Gang of N to make a bestseller book of those...). One design pattern could be always include a default case.

Switches can be done right:

switch (type)
{
    case T_FOO: doFoo(); break;
    case T_BAR: doBar(); break;
    default:
        fprintf(stderr, "You, who are reading this, add a new case for %d to the FooBar function ASAP!\n", type);
        assert(0);
}

This code will point your favorite debugger to the location where you forgot to handle a case. A compiler can force you to implement your interface, but this forces you to test your code thoroughly (at least to see the new case is noticed).

Of course if a particular switch would be used more than one places, it's cut out into a function (don't repeat yourself).

If you want to extend these switches just do a grep 'case[ ]*T_BAR' rn . (on Linux) and it will spit out the locations worth looking at. Since you need to look at the code, you will see some context which helps you how to add the new case correctly. When you use polymorphism the call sites are hidden inside the system, and you depend on the correctness of the documentation, if it exists at all.

Extending switches does not break the OCP too, since you does not alter the existing cases, just add a new case.

Switches also help the next guy trying to get accustomed to and understand the code:

  • The possible cases are before your eyes. That's a good thing when reading code (less jumping around).
  • But virtual method calls are just like normal method calls. One can never know if a call is virtual or normal (without looking up the class). That's bad.
  • But if the call is virtual, possible cases are not obvious (without finding all derived classes). That's also bad.

When you provide an interface to a thirdparty, so they can add behavior and user data to a system, then that's a different matter. (They can set callbacks and pointers to user-data, and you give them handles)

Further debate can be found here: http://c2.com/cgi/wiki?SwitchStatementsSmell

I'm afraid my "C-hacker's syndrome" and anti-OOPism will eventually burn all my reputation here. But whenever I needed or had to hack or bolt something into a procedural C system, I found it quite easy, the lack of constraints, forced encapsulation and less abstraction layers makes me "just do it". But in a C++/C#/Java system where tens of abstraction layers stacked on the top of each other in the software's lifetime, I need to spend many hours sometimes days to find out how to correctly work around all the constraints and limitations that other programmers built into their system to avoid others "messing with their class".

Calmarius
  • 18,570
  • 18
  • 110
  • 157
2

Polymorphism is one of the corner stones of OO and certainly is very useful. By dividing concerns over multiple classes you create isolated and testable units. So instead of doing a switch...case where you call methods on several different types or implemenations you create a unified interface, having multiple implementations. When you need to add an implementation, you do not need to modify the clients, as is the case with switch...case. Very important as this helps to avoid regression.

You can also simplify your client algorithm by dealing with just one type : the interface.

Very important to me is that polymorphism is best used with a pure interface/implementation pattern ( like the venerable Shape <- Circle etc... ) . You can also have polymorphism in concrete classes with template-methods ( aka hooks ), but its effectiveness decreases as complexity increases.

Polymorphism is the foundation on which our company's codebase is built, so I consider it very practical.

QBziZ
  • 3,170
  • 23
  • 24
1

This is mainly to do with encapsulation of knowledge. Let's start with a really obvious example - toString(). This is Java, but easily transfers to C++. Suppose you want to print a human friendly version of an object for debugging purposes. You could do:

switch(obj.type): {
case 1: cout << "Type 1" << obj.foo <<...; break;   
case 2: cout << "Type 2" << ...

This would however clearly be silly. Why should one method somewhere know how to print everything. It will often be better for the object itself to know how to print itself, eg:

cout << object.toString();

That way the toString() can access member fields without needing casts. They can be tested independently. They can be changed easily.

You could argue however, that how an object prints shouldn't be associated with an object, it should be associated with the print method. In this case, another design pattern comes in helpful, which is the Visitor pattern, used to fake Double Dispatch. Describing it fully is too long for this answer, but you can read a good description here.

Nick Fortescue
  • 43,045
  • 26
  • 106
  • 134
0

It works very well if you understand it.

There are also 2 flavors of polymorphism. The first is very easy to understand in java-esque:

interface A{

   int foo();

}

final class B implements A{

   int foo(){ print("B"); }

}

final class C implements A{

   int foo(){ print("C"); }

}

B and C share a common interface. B and C in this case can't be extended, so you're always sure which foo() you're calling. Same goes for C++, just make A::foo pure virtual.

Second, and trickier is run-time polymorphism. It doesn't look too bad in pseudo-code.

class A{

   int foo(){print("A");}

}

class B extends A{

   int foo(){print("B");}

}

class C extends B{

  int foo(){print("C");}

}

...

class Z extends Y{

   int foo(){print("Z");

}

main(){

   F* f = new Z();
   A* a = f;
   a->foo();
   f->foo();

}

But it is a lot trickier. Especially if you're working in C++ where some of the foo declarations may be virtual, and some of the inheritance might be virtual. Also the answer to this:

A* a  = new Z;
A  a2 = *a;
a->foo();
a2.foo();

might not be what you expect.

Just keep keenly aware of what you do and don't know if you're using run-time polymorphism. Don't get overconfident, and if you're not sure what something is going to do at run-time, then test it.

Martin York
  • 257,169
  • 86
  • 333
  • 562
0

If you are using switch statements everywhere you run into the possibility that when upgrading you miss one place thats needs an update.

0

I must re-iterate that finding all switch statments can be a non trivial processes in a mature code base. If you miss any then the application is likely to crash because of an unmatched case statement unless you have default set.

Also check out "Martin Fowlers" book on "Refactoring"
Using a switch instead of polymorphism is a code smell.

-2

It really depends on your style of programming. While this may be correct in Java or C#, I don't agree that automatically deciding to use polymorphism is correct. You can split your code into lots of little functions and perform an array lookup with function pointers (initialized at compile time), for instance. In C++, polymorphism and classes are often overused - probably the biggest design mistake made by people coming from strong OOP languages into C++ is that everything goes into a class - this is not true. A class should only contain the minimal set of things that make it work as a whole. If a subclass or friend is necessary, so be it, but they shouldn't be the norm. Any other operations on the class should be free functions in the same namespace; ADL will allow these functions be used without lookup.

C++ is not an OOP language, don't make it one. It's as bad as programming C in C++.

coppro
  • 14,338
  • 5
  • 58
  • 73
  • Bjarne Stroustroup would be very surprised to learn that C++ is not an OOP language! http://www.amazon.com/C-Programming-Language-Special-3rd/dp/0201700735/ref=sr_1_1?ie=UTF8&s=books&qid=1229196767&sr=1-1 – Steven A. Lowe Dec 13 '08 at 19:33
  • I tend to agree. People have down-marked the answer. Class is encapsulation. Looking at many of the std library classes, they're all small and compact, each designed to serve a singular purpose. –  Dec 26 '20 at 22:43