(Heavily from https://www.fluentcpp.com/2016/12/15/respect-levels-of-abstraction/, by Jonathan Boccara)

What if there was only one principle to know instead of plenty of best practices?

I believe this principle exists: it consists of Respecting levels of abstraction.

Respecting Levels of Abstraction

This is the one principle to rule them all, because applying it automatically applies all the above best practices, and even more of them. When you follow it, your code writes itself out well naturally.

Respecting levels of abstraction means that all the code in a given piece of code (a given function, an interface, an object, an implementation) must be at the same abstraction level. Said differently, at a given abstraction level there mustn’t be any code coming from another level of abstraction.

A given level of abstraction is characterized by what is done in it. And to go from a given level of abstraction to the next lower one, the less abstract one is how the more abstract one is implemented.

So the crucial question to constantly ask yourself when you design or write code is: In terms of what am I coding here?, to determine which level of abstraction you are coding at, and to make sure you write all surrounding code with a consistent level of abstraction.

One principle to rule them all

I deem the Respect of levels of abstraction to be the most important principle in programming, because it automatically implies many other best practices. Let’s see how several well-known best practices are just various forms of respecting levels of abstractions.

Polymorphism

Maybe the first thing you thought of when reading about abstraction is polymorphism.

Polymorphism consists of segregating levels of abstraction.

Indeed, for a given interface (or abstract class) and a concrete implementation, the base class is abstract, while the derived implementation is less abstract.

Note that the derived class is still somewhat abstract though, since it is not expressed in terms of 0s and 1s, but it is at an inferior level of abstraction than the base class. The base class represents what the interface offers, and the derived class represents how it is implemented:

Good Naming

Good naming is in fact giving names that are consistent with the abtraction level they are used in.

Let’s take the example of a class in charge of maintaining a caching of values. This class lets its clients add or retrieve values of type V, with keys of type K. It can be implemented with a map<K,V>.

Imagine now that we want the interface to be able to provide the whole set of results for all stored keys at once. Then we add a method to the interface. How should we name this method? A first try may be getMap.

....
const std::map<K,V>& getMap() const { return my_map; }
....

But as you might feel, getMap is not a good name. And the reason why it isn’t is because at the abstraction level of the caching interface, Map is a term of how, and not of what, so not at the same abstraction level. Calling it getMap would mix several abstraction levels together.

A simple fix would be to call it getAllValues for instance. Values is a consistent term with the level of abstraction of the caching interface, and is therefore a name that is more adapted than Map.

Encapsulation

Concerning the example above, isn’t it a violation of encapsulation to provide the map of results to the outside of the class in the first place? Actually the answer depends on whether the concept of a results container is logically part of the abstraction of the class interface.

So breaking encapsulation is providing information that go beyond the abstraction level of the interface.

Cohesion

Now imagine that we added a new method in the caching class to do some formatting on values:

....
static void formatValue(V&);
....

This is obviously a bad idea because this class is about caching values, not about formatting them. Doing this would break the cohesion of the class. In terms of abstraction, even though caching and formatting don’t have a what-how relationship, they are two different abstractions because they are in terms of different things.

So cohesion consists of having only one abstraction at a given place.

Conciseness, Readability*

[...]

Expressiveness

Last but not least, expressiveness, which is the focus of Fluent C++.

[...]