📜 ⬆️ ⬇️

Super expressive code involving levels of abstractions

I bring to your attention the translation of the article Super expressive code


With this post, I want to suggest a technique for transforming obscure code into an elegant and expressive, based on levels of abstraction.


Problem


Below is the problem code. We will transform this inexpressive and incomprehensible code into a clear and elegant.


A user of our app plans a trip through several cities in the country.


He goes immediately from one city to another without stopping, if they are close enough (say, at a distance of up to 100 km), otherwise he makes exactly one stop between cities.


Suppose we have a planned route in the form of a collection of cities.


Our task is to calculate the required number of stops, saving time on the train.


The application already has a class City, which describes the city on the route. City provides its geographic attributes, among which is the location implemented by the Location class. An object of the Location class can calculate the length of the route to any other Location object on the map.


class Location { public: double distanceTo(const Location &other) const; ... }; class GeographicalAttributes { public: Location getLocation() const; ... }; class City { public: const GeographicalAttributes &getGeographicalAttributes() const; ... }; 

Now it is proposed to calculate the required number of stops that the user will have to make:


 #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 will surely agree that this piece of code is rather vague, and the average reader of this code will spend some time trying to understand what is happening in the code. Unfortunately, this is a non-fictional example of what we can find in the code of a real application. And if such code is located in a place that is often studied and changed, ambiguity becomes a real problem.


Let's work on this piece of code and turn it into your asset.


Create a clear code


Creating a clear code is one of the positive consequences of attracting levels of abstractions, which, I believe, is an important principle of good code design.


In many cases, the non-use of levels of abstraction occurs where the code of the lower level is inserted among the code of the middle or high level. In other words, the problem is the code that describes how the result is produced instead of what is being done. To improve such code, we need to increase the level of abstractions.


To do this, we can use the following technique:


Determine what things the code does and replace them with meaningful labels.


This will lead to a significant improvement in code clarity.


The problem with the above code is that it does not tell what it means - this code is not clear. Let's apply the above technique to improve the clarity of the code: let's determine what things the code does and mark each such thing.


Let's start with the logic of the cycle.


 for (std::vector<City>::const_iterator it1 = route.begin(), it2 = route.end(); it1 != route.end(); it2 = it1, ++it1) { if (it2 != route.end()) { 

Perhaps you already know this technique used in the code. This trick is used to manipulate adjacent items in the collection. it1 starts at the beginning of the collection, it2 points to an element right in front of it1 as it goes through the entire unidirectional collection. At the beginning, it2 is initialized by the end of the collection, then in the body of the loop it is checked that it2 no longer indicates the end of the collection, and in this case calculations are made.


We could not immediately say that this code is understandable. But after describing the trick, we can definitely say what he does: he performs manipulations with two adjacent elements at a time.


Let's now consider another piece of code in the condition:


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

By itself, this code is quite easy to analyze and understand what it does. It determines that the distance between the two cities is greater than MaxDistance.


And finally, let's analyze the rest of the code, the nbBreaks variable:


 int nbBreaks = 0; for (...) { if(...) { ++nbBreaks; } } return nbBreaks; 

This code increments the variable each time the condition is met. It calculates the number of times the condition has been met.


As a result, we obtain such labels that describe what the function does:



After the analysis is done, there is only one step left before turning the obscure code into expressive code.


The technique consists in assigning a label to each entity implemented by the code and replacing the corresponding code with this label. Here we do the following:



This is what happens after replacing the code with tags:


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

(Note: count_if from STL takes two iterators from the beginning to the end of the collection. It uses the count_if wrapper over std :: count_if, which passes the beginning and end of the collection to the standard std :: count_if to C ++ 17.)


This code clearly shows what it does and does not mix levels of abstractions. For this reason, it is much more expressive and clearer than the original code. The original code only described how it does its work, and the rest of the understanding remained on the reader.


This technique can be applied to many parts of obscure code in order to convert it to clear. It can also be used for other programming languages. The next time you stumble upon an obscure code that you want to refactor, try to identify the things the code does and give them names. You will be amazed at the result achieved.


')

Source: https://habr.com/ru/post/341006/


All Articles