Last Revision — April 19, 2022
3 Min Read
Repeated Switching Switch Statement Conditional Complexity Prefer Polymorphism to if/else or switch/case
Object Oriented Abusers
Conditional Logic
Within a Class
- Callback Hell (family)
- Combinatorial Explosion (family)
- Flag Arguments (caused)
- Loops (caused)
- Null Check (caused)
- Long Parameter List (caused)
Steve Smith in course (2013):
"Refactoring Fundamentals"
Martin Fowler in book (2018):
"Refactoring: Improving the Design of Existing Code (3rd Edition)"
Martin Fowler in book (1999):
"Refactoring: Improving the Design of Existing Code"
In data-oriented programming, the usage of switch
-like statements (lengthy, cascading if
statements or switch
/case
) should be relatively rare. One such switch usually executes code scattered around the code base and should usually be replaced with a polymorphism solution.
This smell was phrased initially as Switch Statement by Fowler and Beck back in 1999 [1]. One year later, Mäntylä noted that such a name is misleading since switch statements do not necessarily imply a code smell but rather just in the situation when they are used instead of a viable polymorphism solution. [2] Fowler, 14 years later, in his newest book in 2018, changed the name, suggesting Repeated Switching, agreeing that, fortuitously due to the way he initially phrased it, conditional statements got a bad reputation, while he never unconditionally opposed the existence of all if
-s and switch
-es. [3] In the "Clean Code" by Robert Martin, the smell was called "Prefer Polymorphism to if/else or switch/case", which is lengthy, but it hits the nail on the head. [4]
I provide a typical example of this issue on an Exporter
class with different file formats. A new elif
has been added for each new feature instead of using an Object-Oriented solution like the Factory Method.
Keeping a class focused on a single concern is vital to make it more robust. Martin Fowler defines responsibility as a reason to change, concluding that “A class should have only one reason to change.” [3]. An example that violates this would be a class that prints a table that handles both the contents of the cells and the styling of the table.
Another situation, which is not explicitly mentioned in the sources, would be a nested Try
and Except
/Catch
"checklist", where numerous error-catching blocks are used instead of one generalized for the situation at hand.
The most common way such a smell can be created is when a conditional switch behavior is used instead of creating a new class. The first time it happens is not yet a "scent-ish" smell, but this immediately becomes a saturated red flag as soon as it is done the second time [5]. Logic blocks grew over time more extensive, and no one bothered to implement an Object-Oriented Programming style alternative like decorator, strategy, or state. It was easier to add another else if
.
The class should be open for extension but closed for modification
If statement blocks make things harder to understand as they require to think about all the possible paths the logic can go.
Most likely, there is a way to execute one method on a polymorphic class instead of switching cases.
Each subsequent if branch needs each subsequent test.
class Exporter:
def export(self, export_format: str):
if export_format == 'wav':
self.exportInWav()
elif export_format == 'flac':
self.exportInFlac()
elif export_format == 'mp3':
self.exportInMp3()
elif export_format == 'ogg':
self.exportInOgg()
class Exporter:
def export(self, export_format: str):
exporter = self.get_format_factory(export_format)
exporter.export()
def get_format_factory(self, export_format: str):
if export_format in self.export_format_factories:
return render_factory[export_format]
raise MissingFormatException
...