The false abstraction antipattern

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.

Single implementation

An abstract type that has only a single implementation may be a false abstraction. These often following a naming convention of Fluff and 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.

Poorly named

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.

22 replies »

  1. Thank you. I am happy someone finally said it. My impression is that some people believe that only having any abstraction inside makes the program better. And having dozens of any abstractions make the program great.

  2. We used to have this large abstract class called ‘Editor’ for our game engine’s editor. The only instance of its implementation was entirely in a cpp file, the class named EditorMain (basically EditorImpl). Every time anyone wanted to add anything accessible to the Editor, they’d have to put it on the interface and then re-implement it on EditorMain, exactly as you mentioned. Moreover, because EditorMain was all in the cpp, any auxiliary structs/classes it used were generally also defined there. The second we wanted to promote something to being a function on Editor, we’d have to pull out a bunch of struct/class definitions from the cpp and put them into the header / add more abstract functions. Realistically nobody else would ever implement that interface (not even for a junior version of the editor). It was hell.

    Definitely an anti-pattern.

  3. I think many of the people who really get into computer programming have what I call “an Engineering Mind.” One characteristic of Engineers is a love of complexity for complexity’s sake. I think many of us know the sheer joy of a highly complex system… but it’s something we have to resist in our own work. KISS! Keep It Super Simple!

    • I always though an engineer’s mindset would be more minimalism. The ones I know tend to be building things where cost is an issue.

      But I’m not sure it’s a love of complexity, so much as a fear of the unknown, or lack of self confidence. I often find patterns are applied since they offer a certain level of comfort. The feeling that code is okay since my data structures are all coming from popular programming books. It gives the programmer a justification for their code.

    • Sure, cost is a reality, but it’s a constraint imposed from without. (The Woz’s design for the first Apple might be a classic example.) That might even be why absurd designs pop up in software more than mechanical or electrical engineering — a ridiculously overly-complicated design doesn’t cost much (at first).

      And I agree that Apprentice, and even Journeyman, coders might operate from fear, but I don’t really consider those as “grown up” programmers. They haven’t matured into the kind of programmer they’ll actually be until their experience frees them from that fear. (The naturally born programmers… they never had it in the first place. That might actually be a good litmus test for a Real Programmer. There are so many out there I’ve met who shouldn’t be in the profession.)

  4. I’ll refer co-workers to this post. I’ve said in the past (maybe not so concisely) “abstract when there are multiple implementations or the abstraction is an interface and the single implementation implements other interfaces (beware of SRP)”. That second part is there for a when you have instance holding data and want to encapsulate what’s needed by multiple sub-systems.

  5. One notable exception to this anti-pattern is for code generation, e.g., entities where columns, keys and other behaviors are created from a schema or interface definition file (to the base class), and then custom behavior can be added (in a child class). From an OO perspective it isn’t very proper, but can be very helpful when dealing with a DB with dozens or hundreds of tables that changes over time.

    • For message schemas with external parties that seems ok, if you want an object representation to work from (my preference). You mention a lot of relational databases concepts, don’t principles indicate that to be flawed e.g. If you do command query separation you can get the schema to be automatically validated against the sprocs, funcs and views, and have the them in turn automatically validated against the code.

  6. I agree that too much abstraction can be a problem, but it really is a complex issue. If you follow YAGNI all the time then in the cases where it turns out that you do need it you are forced to insert a layer of abstraction where there wasn’t one before. It’s easier to do that right from the start rather than retrofit your code. The developer who creates the system in the first place cannot always assume they won’t need that abstraction. Instead, he or she needs to make an educated guess. Sometimes the guess is right and it turns out that all that effort to maintain the interface was worth it. Other times it turns out that you never have a second implementation and it has all been a waste of time. It’s a really tough call sometimes.

    In a lot of cases I think it makes sense to keep the interface, but make a better name for the implementations. For example, if you have the interface UsersRepository then you should never have a class called UsersRepositoryImpl or StandardUsersRepository. Instead, it should be called PostgresUsersRepository or something like that which actually tells you something useful and communicates to developers that you want other classes which consume the interface NOT to know about postgres. Then it becomes a sort of documentation that helps other developers maintain the decoupling that you originally intended to have.

    • No, it should not hard be to insert a layer of abstraction. I’ve never had a problem with this even on complex code bases. If the code is otherwise well written, clean, and has test cases, it is not difficult to add in abstraction after later.

      Decoupling doesn’t need an interface. You can also make use of public/private methods to mark the external API.

    • Interesting that you think that way. The reason I have always looked at retrofitting abstraction as expensive is because I may actually need to design the interface entirely differently if I have a wider set of implementations in mind (including unknown implementations). If I actually change the programming interface later on then there could be a lot of code to refactor which made invalid assumptions.

      If you have a class then the code that uses that class is always written against an interface, it’s just that the interface might be the set of public members on that class. So to me it’s not about whether you have an explicit interface, it’s whether the design of that interface is sufficiently generic such that any future implementations will not require more than a find/replace on the code that uses it.

      If I’m designing a tool that accesses a database then I might design the internal interfaces, or even the public API differently depending on the database technology used (eg: RDBMS vs document database). If I know up front that I need to support both then I will use a much higher level abstraction. That is the type of abstraction I’m referring to that would be hard to add in later.

  7. IMO, I am dealing with the polar opposite situation. bunches of classes with same suffix but not even a marker interface to unite them all. You outta write an article about that too.

    • Unfortunately that also happens. Often it happens in combination: lots of similar classes, each with their own independent interface.

  8. While I agree that over abstraction is bad, making a blanket statement like ‘dont abstract until it is needed’ can be easily misinterpreted. separation of concern does need to be abstracted still especially when one takes into consideration aspects that will/can/may move to external tooling. Not to take into consideration abstraction for these elements is to doom your application and even tooling.

    • No, separations of concern do not need abstraction. Class A can depend on Class B without B needing to be an abstract type. Implementation details can be hidden with private functions and types.

    • Cross-cutting concerns need separation especially when they are potentially used by external tooling in your architecture as you scale. If you don’t take into consideration to abstract these separations of concern so that they can be externalized/synchronized/shared then you will be painting yourself into a corner.

  9. Thanks for this. You hit the nail on the head. Based on comments, I fear you may have been to subtle, though.

    Folks, an interface does not an abstraction make. Prefer finely grained role interfaces to fat header interfaces. If one has a problem introducing abstraction as needed, this is indicative of other problems, not a failure to preemptively introduce interfaces/abstract classes that do not represent meaningful abstractions.

    Thanks again for putting a name to this. Hopefully it advances the cause of folks understanding what a meaningful abstraction is.

    • There are two main themes to the comments I’ve received, here and on Reddit.

      1. That abstraction at a later date is a costly if not impossible endeavour. I’m going to agree with you on this one. If you can’t add abstraction later it’s indicative of other problems in your code, since you likely can’t do any kind of refactoring at all.

      2. That I’m advocating against limited interfaces, api segregation, library protection, or separations of concern. Which of course I’m not, I just don’t believe that abstraction is required to do any of this. Sometimes is the way to go, but usually on the first round of coding it isn’t. (Though I also think people often worry about these things too early in a project.)

      Perhaps I can do some followup articles.

  10. There are some good reasons why this is not really an anti pattern:

    1. having a separate interface reduces dependencies.
    2. reducing dependencies improves compile / build times.
    3. interfaces may be necessary for testing, as some mocking tools require interfaces

    • All three of those sound like valid reasons to abstract, using the terminology of the article “true” abstractions. For me this article is clearly about when abstraction is not done specifically and explicitly to define those kind relationships for encapsulation but arbitrarily.

      Maybe a better name/title would be the arbitrary abstraction antipattern and/or the ugly mirror abstraction antipattern (like the unit test one).

    • An interface does not reduce dependencies per se. The code that uses the interface depends on this interface, and to get a working system you need an implementation. An, that is the point of this blog post, if there is only one implementation, you have decoupled nothing, but introduced a useless interface!

      Maybe you could reduce build times by using interface, but I wouldn’t clutter my code for that reason with otherwise useless interfaces.

      Using interfaces only for mocking, is a poor reason. It is like having methods only for testing in your production code. It is much better, to use appropriate mocking tools like JMockit (for Java).

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s