Skip to main content

Abstractions as communication 📣

A megaphone with abstract output.

Many people associate abstraction and generalization with reuse; that the whole point of generalizing or creating an abstraction is to be able to reuse it. I would like to highlight another reason why you might want to create abstractions. But first, let us start with another sound piece of advice.

Avoid premature generalization #

It is true that building general solutions based on abstractions is an important tool in the developer’s toolbox when it comes to handling multiple similar use cases. However, because of the cost of unnecessary abstractions, it is important to design for today and avoid premature generalization.

Imagine a scenario where your system needs to save files to disk. If your system only needs to save files in a single format, you don’t need an extensible system. You may be better off with a direct, specialized implementation. Even if you add another format, it might be perfectly fine to just add another code path without any shared abstraction.

However, once you need to add file more formats, a generalized solution which can handle any number of formats may be the best option. This is the typical “abstraction for reuse” use case, guided by the rule of three.

Use abstractions to communicate #

With that said, there is another reason why you may want to generalize and create abstractions: to communicate intent to your fellow programmers. I like David R. MacIver’s way to put it.

When you introduce an abstraction, it has to be an improvement at each call site. That is, even if you never reuse it, it should be [to] make your code better.

Looking at the previous example, it may actually make the code clearer to introduce the generic file export abstraction from the beginning. Not necessarily because it is extensible (you may never need to add more formats anyway), but because the design may become clearer and better communicate the intent to the reader.

Glyn Normington also described this well.

The trick is to consider what general extension the current extension might be part of and then ask the question: is the general extension simpler than the specific extension needed right now? How can we judge simplicity? Two clues are if it’s easier to document or easier to test.

Separating the formatting and file management from the primary business logic is also an example of the separation of concerns principle at work.

As always, there are pros and cons for both alternatives and it becomes a judgement call of the developer.

Would you created a single-use abstraction “just” to make the code clearer?

Austin Heller at : That makes a lot of sense to me. I always (literally 100% of the time) create an abstraction around external systems like the database, APIs, event queues, etc. because it not only makes the code that utilizes those systems far easier to unit test but it allows me to define and restrict how those systems are affected by the larger system.
I find that we, as developers, better understand how to use a thing that only has a specific, defined number of ways to use it. Creating an abstraction over a database connection instead of giving out the connection allows for specific interactions to be managed and utilized in obvious ways.