Last Revision — April 19, 2022
3 Min Read
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
Change Preventers
Responsibility
Within a Class
- 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)
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"
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?
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.
Code written "too literally" is closed on extendibility. Disregarding any abstractions on the implementation ideas makes it hard to introduce new features.
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.
Methods with more than one layer of abstraction are most likely doing more than one thing.
from abc import ABC # Abstract Base Class
class Instrument(ABC):
adapter: ConnectionAdapter
def reset(self):
self.write("*RST")
def write(self, command):
...
from abc import ABC
class Instrument(ABC):
adapter: ConnectionAdapter
def reset(self):
self.adapter.write("*RST")