Super expressive code by Raising Levels of Abstraction

In this post I would like to propose a technique based on levels of abstraction to transform an obscure piece of code into expressive and elegant one.

You’ve been quite a few to take up the Christmas Break Challenge for writing expressive code, by posting a comment on the challenge post or by chipping in on Reddit. Thanks a lot to everyone! The various proposals spun up interesting discussions and participants could exchange and learn from each other.

The winner

The winner of the challenge is Fred Tingaud. His solution is remarkably simple and shows clearly what the code is meaning to do, which is why it came out first in the selection process. Congratulations Fred!

You can find Fred online on Twitter @fredtingaud if you want to congratulate him too.

Many of you expressed a very positive feedback on this challenge for expressive code. Therefore, such challenges for the most Expressive Code will be put up regularly on Fluent C++. This way we will continue to learn from each other and strive for the most expressive code.

The case

Here is the code of the challenge. We’ll solve it with the technique to transform unclear code into expressive and elegant code. If you’ve already taken up the challenge then you can just skip to the next section where the technique is exposed.

The user of your application is planning a trip across several cities in the country.

He would drive straight through from one city to the next if they are close enough (say under 100 kilometers), otherwise he would take a break on the road between two cities. The user doesn’t take more than one break between two cities.

Let’s say that we have the planned route in the form of a collection of cities.

Your objective is to determine how many breaks the driver has to take, which can be useful for budgeting time for them for example.

This application has existing components, such as the class City that represents a given city on the route. City can provide its geographical attributes, amongst which its location which is represented by a class Location. And a object of type Location can itself compute the driving distance to any other Location on the map:

class Location { public: double distanceTo(const Location& other) const; ... }; class GeographicalAttributes { public: Location getLocation() const; ... }; class City { public: GeographicalAttributes const& getGeographicalAttributes() const; ... }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Location { public : double distanceTo ( const Location & other ) const ; . . . } ; class GeographicalAttributes { public : Location getLocation ( ) const ; . . . } ; class City { public : GeographicalAttributes const & getGeographicalAttributes ( ) const ; . . . } ;

Now here is the current implementation for working out the number of breaks the user has to take:

#include <vector> int computeNumberOfBreaks(const std::vector<City>& route) { static const double MaxDistance = 100; int nbBreaks = 0; for (std::vector<City>::const_iterator it1 = route.begin(), it2 = route.end(); it1 != route.end(); it2 = it1, ++it1) { if (it2 != route.end()) { if(it1->getGeographicalAttributes().getLocation().distanceTo( it2->getGeographicalAttributes().getLocation()) > MaxDistance) { ++nbBreaks; } } } return nbBreaks; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <vector> int computeNumberOfBreaks ( const std :: vector < City > & route ) { static const double MaxDistance = 100 ; int nbBreaks = 0 ; for ( std :: vector < City > :: const_iterator it1 = route . begin ( ) , it2 = route . end ( ) ; it1 != route . end ( ) ; it2 = it1 , ++ it1 ) { if ( it2 != route . end ( ) ) { if ( it1 -> getGeographicalAttributes ( ) . getLocation ( ) . distanceTo ( it2 -> getGeographicalAttributes ( ) . getLocation ( ) ) > MaxDistance ) { ++ nbBreaks ; } } } return nbBreaks ; }

You’ll probably admit that this piece of code is fairly obscure and that an average reader would need to spend some time working out what’s happening in it. Unfortunately this is the kind of thing you can find in real-life applications. And if this piece of code is located in a place of the codeline that is often read or updated then it becomes a real problem.

Let’s work on this piece of code to transform it into an asset for your codeline.

Making code expressive

Making code expressive is one of the Good Things that happen by Respecting Levels of Abstraction, which I deem is the most important principle for designing good code.

In many cases of levels of abstractions not respected, the problem comes from a lower level code that is in the middle of a higher level layer of the stack. Said differently, the problem is code that describes how it performs an action rather than what action it performs. To improve such a piece of code, your need to raise its level of abstraction.

And to do so you can apply the following technique:

Identify what things the code does, and replace each one of them with a label.

This has the effect of dramatically improving expressiveness of the code.

The problem of the above piece of code is that it doesn’t say what it means – this code is not expressive. Let’s use the previous guideline to improve expressiveness, that is to say let’s identify what things the code does, and put a label on each one of them.

Let’s start with the iteration logic:

for (std::vector<City>::const_iterator it1 = route.begin(), it2 = route.end(); it1 != route.end(); it2 = it1, ++it1) { if (it2 != route.end()) { 1 2 3 4 5 6 for ( std :: vector < City > :: const_iterator it1 = route . begin ( ) , it2 = route . end ( ) ; it1 != route . end ( ) ; it2 = it1 , ++ it1 ) { if ( it2 != route . end ( ) ) {

Maybe you have seen this technique applied before. This is a trick to manipulate adjacent elements in a collection. it1 starts at the beginning, and it2 point to the element right before it1 all along the traversal. To initialise it2 with something we start by setting it at the end of the collection, and check that it2 is no longer at the end within the body of the loop to actually start the work.

No need to say that this code is not exactly expressive. But now we have determined what it meant to do: it aims at manipulating consecutive elements together.

Let’s tackle the next piece of the code, in the condition:

it1->getGeographicalAttributes().getLocation().distanceTo( it2->getGeographicalAttributes().getLocation()) > MaxDistance 1 2 it1 -> getGeographicalAttributes ( ) . getLocation ( ) . distanceTo ( it2 -> getGeographicalAttributes ( ) . getLocation ( ) ) > MaxDistance

Taken on its own, this one is fairly easy to analyse as to what it means to do. It determines whether two cities are farther away than MaxDistance.

Let’s finish the analysis with the remaining part of the code, the variable nbBreaks:

int nbBreaks = 0; for (...) { if(...) { ++nbBreaks; } } return nbBreaks; 1 2 3 4 5 6 7 8 9 int nbBreaks = 0 ; for ( . . . ) { if ( . . . ) { ++ nbBreaks ; } } return nbBreaks ;

Here the code increments the variable depending on a condition. It means to count the number of times a condition is satisfied.

So in summary here are the labels that describe what the function does:

Manipulate consecutive elements together,

Determine if cities are farther away than MaxDistance,

Count the number of times a condition is satisfied.

Once this analysis is done, it is only a matter of time before the obscure code turns into a meaningful one.

The guideline was to put a label over each of the things the code does, and replace the corresponding code with it. Here we are going to do the following:

For manipulating consecutive elements, we can create a component that we would call “consecutive”, and that would transform a collection of elements into a collection of element pairs , each pair having an element of the initial collection and the one next to it. For instance, if route contains {A, B, C, D, E}, consecutive(routes) would contain {(A,B), (B,C), (C, D), (D, E)}.

You can see my implementation here . One such adaptor that creates pair of adjacent elements has recently been added to the popular range-v3 library under the name of sliding . More on the important topic of ranges in this post

For determining whether two consecutive cities are farther away from each other than MaxDistance, we can simply use a function object (functor) that we would call FartherThan. I recognize that since C++11 functors have mostly been replaced by lambdas but here we need to give a name to the thing. Doing this elegantly with a lambda requires a bit more work and we explore this in details in a dedicated post :

class FartherThan { public: explicit FartherThan(double distance) : m_distance(distance) {} bool operator()(const std::pair<City, City>& cities) { return cities.first.getGeographicalAttributes().getLocation().distanceTo( cities.second.getGeographicalAttributes().getLocation()) > m_distance; } private: double m_distance; }; 1 2 3 4 5 6 7 8 9 10 11 12 class FartherThan { public : explicit FartherThan ( double distance ) : m_distance ( distance ) { } bool operator ( ) ( const std :: pair < City , City > & cities ) { return cities . first . getGeographicalAttributes ( ) . getLocation ( ) . distanceTo ( cities . second . getGeographicalAttributes ( ) . getLocation ( ) ) > m_distance ; } private : double m_distance ; } ;

For counting the number of times a condition is satisfied, we can just use the STL algorithm count_if .

Here is the final result, obtained by replacing the code with the corresponding labels:

int computeNumberOfBreaks(const std::vector<City>& route) { static const double MaxDistance = 100; return count_if(consecutive(route), FartherThan(MaxDistance)); } 1 2 3 4 5 6 int computeNumberOfBreaks ( const std :: vector < City > & route ) { static const double MaxDistance = 100 ; return count_if ( consecutive ( route ) , FartherThan ( MaxDistance ) ) ; }

(note: the native count_if C++ function would take two iterators to a begin and end of collection. The one used here simply calls the native one with the begin and end of the passed range)

This code explicitly shows what things it does and repects levels of abstraction. For this reason, it is much more expressive than the initial one. The initial one only told how it did the work, leaving its reader the rest of the job.

This technique can be applied to many unclear pieces of code, to turn them into very expressive ones. It can even be applied in other languages than C++. So next time you stumble upon obscure code that you want to refactor, think about Identifying what things the code does, and put a label on each one of them. You should be surprised with the results.

(*) the selection process for the code challenge is the following: I personally review all code propositions, although I don’t have the final say: I show various submissions to the youngest person in my team, and he says which one he understands most easily.

Related articles:

Share this post! Don't want to miss out ?