Navs
Code Smells Catalog
Conditional Complexity

Last Revision — April 19, 2022

3 Min Read


  • Also Known As

    Repeated Switching Switch Statement Conditional Complexity Prefer Polymorphism to if/else or switch/case

  • Obstruction

    Object Oriented Abusers

  • Occurrence

    Conditional Logic

  • Expanse

    Within a Class

  • Related Smells

    - Callback Hell (family)
    - Combinatorial Explosion (family)
    - Flag Arguments (caused)
    - Loops (caused)
    - Null Check (caused)
    - Long Parameter List (caused)

  • History

    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"

Conditional Complexity

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.

Causation

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.

Problems

Open-Closed Principle Violation

The class should be open for extension but closed for modification

More Complex APIs

Comprehensibility

If statement blocks make things harder to understand as they require to think about all the possible paths the logic can go.

Unnecessary Indirection

Most likely, there is a way to execute one method on a polymorphic class instead of switching cases.

Increased Test Complexity

Each subsequent if branch needs each subsequent test.

Examples

Smelly

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()

Solution

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

Refactoring:

  • Use a Guard Clause
  • Extract Conditional
  • Replace with Polymorphism
  • Use Strategy Pattern
  • Use Null Object
  • Use Functional Programming Based Solution

Sources
  • [1], [Parentage] - Martin Fowler, "Refactoring: Improving the Design of Existing Code" (1999)
  • [2] - Mika Mäntylä, "Bad Smells in Software - a Taxonomy and an Empirical Study" (2003)
  • [3] - Martin Fowler, "Refactoring: Improving the Design of Existing Code (3rd Edition)" (2018)
  • [4] - Robert Martin, "Clean Code: A Handbook of Agile Software Craftsmanship" (2008)
  • [5] - William C. Wake, "Refactoring Workbook" (2004)
  • [Origin] - Steve Smith, "Refactoring Fundamentals" (2013)