CodeSOD: Switching Your Template
Many years ago, Kari got a job at one of those small companies that lives in the shadow of a university. It was founded by graduates of that university, mostly recruited from that university, and the CEO was a fixture at alumni events.
Kari was a rare hire not from that university, but she knew the school had a reputation for having an excellent software engineering program. She was prepared to be a little behind her fellow employees, skills-wise, but looked forward to catching up.
Kari was unprepared for the kind of code quality these developers produced.
First, let's take a look at how they, as a company standard, leveraged C++ templates. C++ templates are similar (though more complicated) than the generics you find in other languages. Defining a method like void myfunction<T>(T param) creates a function which can be applied to any type, so myfunction(5) and myfunction("a string") and myfunction(someClassVariable) are all valid. The beauty, of course, is that you can write a template method once, but use it in many ways.
Kari provided some generic examples of how her employer leveraged this feature, to give us a sense of what the codebase was like:
enum SomeType{ SOMETYPE_TYPE1 // ... more types here};template<SomeType t>void Function1();template<>void Function1<SOMETYPE_TYPE1>(){ // Implementation of Function1 for TYPE1 as a template specialization}template<>void Function1<SOMETYPE_TYPE2>(){ // Implementation of Function1 for TYPE2 as a template specialization}// ... more specializations herevoid CallFunction1(SomeType type) { switch(type) { case SOMETYPE_TYPE1: Function1<SOMETYPE_TYPE1>(); break; case SOMETYPE_TYPE2: Function1<SOMETYPE_TYPE2>(); break; // ... I think you get the picture default: assert(false); break; }}
This technique allows them to define multiple versions of a method call Function1, and then decide which version needs to be invoked by using a type flag and a switch statement. This simultaneously misses the point of templates and overloading. And honestly, while I'm not sure exactly what business problem they were trying to solve, this is a textbook case for using polymorphism to dispatch calls to concrete implementations via inheritance.
Which raises the question, if this is how they do templates, how do they do inheritance? Oh, you know how they do inheritance.
enum ClassType{ CLASSTYPE_CHILD1 // ... more enum values here};class Parent{public: Parent(ClassType type) : type_(type) { } ClassType get_type() const { return type_; } bool IsXYZSupported() const { switch(type_) { case CHILD1: return true; // ... more cases here default: assert(false); return false; } }private: ClassType type_;};class Child1 : public Parent{public: Child1() : Parent(CLASSTYPE_CHILD1) { }};// Somewhere else in the application, buried deep within hundreds of lines of obscurity...bool IsABCSupported(Parent *obj){ switch(obj->get_type()) { case CLASSTYPE_CHILD1: return true; // ... more cases here default: assert(false); return false; }}
Yes, once again, we have a type flag and a switch statement. Inheritance would do this for us. They're reinvented the wheel, but this time, it's a triangle. An isosceles triangle, at that.
All that's bad, but the thing which elevates this code to transcendentally bad are the locations of the definitions of IsXYZSupported and IsABCSupported. IsXYZSupported is unnecessary, something which shouldn't exist, but at least it's in the definition of the class. Well, it's in the definition of the parent class, which means the parent has to know each of its children, which opens up a whole can of worms regarding fragility. But there are also stray methods like IsABCSupported, defined someplace else, to do something else, and this means that doing any tampering to the class hierarchy means tracking down possibly hundreds of random methods scattered in the code base.
And, if you're wondering how long these switch statements could get? Kari says: "The record I saw was a switch with approximately 100 cases."
[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!