Useless interfaces, factories, and other abstractions are everywhere. They are the result of trying to prematurely generalize code. In a recent article I wrote about how I removed one from Leaf. It was getting in the way of other changes, as antipatterns often do.
The false abstraction is any interface, base class, or adaptor that should not exist. While the author may have been well meaning, they probably violated the YAGNI principle, and now we’re left with harder to maintain code. Let’s look at how to identify this antipattern.
An abstract type that has only a single implementation may be a false abstraction. These often following a naming convention of
FluffImpl. The suffix
Impl, or the full
Implementation is generally an indicator of a false abstraction.
The problem is that we have two types to maintain instead of one. Adding a function to the
Fluff class requires adding the same function to the
FluffImpl class. Sometimes the interface also results in artificial feature freeze on projects: I’ve often seen higher reluctance to change abstract types than their implementation.
This may not be an indicator if there is a strong system reason for having this setup. The only common ones I know are for exporting interfaces from a shared library. In this case it isn’t really an abstraction, but an implementation detail.
A large interface, incomplete implementations
An abstract type with a lot of methods or properties may be a false abstraction. As more detail is added it becomes more concrete, and less flexible.
Complex interfaces become harder to implement over time. If it becomes too difficult to add new implementations then the abstraction has somewhat lost its purpose. Often we find that not all implementations actually implement the complete interface. Several methods raise an error or only support part of the functionality. This makes using implementations very error prone.
Correct abstractions are focused and strive to have the minimal necessary interface.
False abstractions often have bad names. This is an indication that the original author was unable to isolate the purpose of the type and thus couldn’t come up with a good name for it. Abstractions must be explainable even when isolated from their implementations.
A red flag here is a type that has the
Interface suffix. Be watchful of something like
FluffInterface implemented by a concrete type
Fluff. Even if it isn’t a single implementation it indicates the interface can’t be described on its own.
The problem with poor names is they confuse code. They either reveal nothing about what the type does, or they leave one wondering whether a different implementation is actually possible.
Get rid of them
False abstractions have the habit of complicating other refactoring. We have to deal with a limited interface and artificial constraints. Working around the abstraction is often the greater bulk of the work. If a false abstraction is impeding you, get rid of it.
Don’t create them either! Abstractions are not inherently valuable. The code is not cleaner because it has an interface and implementation. Creating an abstraction based on a future scenario is wasted effort. Wait until the need arises before creating the abstract type. I’ve never had problems adding abstractions later.