Navs
Code Smells Catalog
Dubious Abstraction

Last Revision — April 19, 2022

3 Min Read


  • Also Known As

    Inconsistent Abstraction Levels Functions Should Descend Only One Level of Abstraction Code at Wrong Level of Abstraction Choose Names at the Appropriate Level of Abstraction

  • Obstruction

    Change Preventers

  • Occurrence

    Responsibility

  • Expanse

    Within a Class

  • Related Smells

    - Speculative Generality (family)
    - Long Method (co-exist)
    - Large Class (co-exist)
    - Refused Bequest (co-exist)
    - Oddball Solution (causes)
    - Required Setup/Teardown Code (causes)
    - Side Effects (caused)
    - Speculative Generality (antagonistic)

  • History

    Marcel Jerzyk in thesis (2022): "Code Smells: A Comprehensive Online Catalog and Taxonomy"

    Steve Smith in course (2013): "Refactoring Fundamentals"

    Robert C. Martin in book (2008): "Clean Code: A Handbook of Agile Software Craftsmanship"

Dubious Abstraction

The smaller and more cohesive class interfaces are the better because they tend to degrade over time. They should provide a constant abstraction level. Function interfaces should be one level of abstraction below the operation defined in their name. Robert Martin describes the above sentences as three separate code smells: Functions Should Descend Only One Level of Abstraction, Code at Wrong Level of Abstraction, Choose Names at the Appropriate Level of Abstraction [1]. He observed that people are having trouble with it and often mix abstraction levels. Steve Smith, in his course, uses the term "Inconsistent Abstraction Levels".

I like the smells in the granularized form presented by Martin as they address the issue directly and specifically. The name Inconsistent Abstraction Levels still holds the idea, but it might be misinterpreted by just recalling the meaning through its title. I suspect that it might create a situation where somewhere out there, in at least one codebase, someone might win an argument with a non-inquisitive individual, thus leaving the abstraction levels consistent... but consistently off. I wish no one ever heard, "that is how it always has been, so it must continue to be done that way".

This frequent wrong selection of abstractions is why I decided to rename it to Dubious Abstraction directly addressing the potential causation of the smell - to think about the code that one just wrote. Fowler says that "there is no way out of a misplaced abstraction, and it is one of the hardest things that software developers can do, and there is no quick fix when you get it wrong". Dubious Abstraction is supposed to provoke the question as soon as possible - "Is it dubious?", taking a second to think about the code at hand and then move on or immediately refactor if something seems fishy: Is the Instrument really querying this message? Or is it the connection device doing it? Maybe I should extract it? Is percentFull a method of Stack for sure?

Causation

It is difficult to grasp whether the abstraction or naming the developer gives to various entities is proper for psychological reasons, like Tunnel Vision. A correct solution requires stepping out of the box to think whether the abstraction is accurate, and that requires continuous and conscious mental action to be undertaken.

Problems

Extendibility

Code written "too literally" is closed on extendibility. Disregarding any abstractions on the implementation ideas makes it hard to introduce new features.

Comprehensibility

Creating context without specifying ungeneralised concepts might be hard to follow. Varying abstraction levels make it challenging to develop a mental map of the code workflow.

Single Responsibility Principle Violation

Methods with more than one layer of abstraction are most likely doing more than one thing.

Example

Smelly

from abc import ABC  # Abstract Base Class

class Instrument(ABC):
    adapter: ConnectionAdapter

    def reset(self):
        self.write("*RST")

    def write(self, command):
        ...

Solution

from abc import ABC

class Instrument(ABC):
    adapter: ConnectionAdapter

    def reset(self):
        self.adapter.write("*RST")

Refactoring:

  • Extract Superclass, Subclass, or new Class

Sources
  • [1] - Robert Martin, "Clean Code: A Handbook of Agile Software Craftsmanship" (2008)
  • [Origin] - Marcel Jerzyk, "Code Smells: A Comprehensive Online Catalog and Taxonomy" (2022)
  • [Parentage2] - Steve Smith, "Refactoring Fundamentals" (2013)
  • [Parentage1] - Martin Fowler, "Refactoring: Improving the Design of Existing Code" (1999)