
My son, like many boys, likes cars. And the more they are unusual and - the more they like. When we walk down the street, and an evacuator or a snow removal vehicle passes by, he invariably pulls my hand, points to the object of interest to him and says: “Dad, bl-r!”. He says so because he is one year old and the above two words make up 40% of his vocabulary. However, in general, the idea is clear - pay attention to the car. Let's think about how a child aged 8-10 years would say the same to his peer. Something like, “Wow, look what a cool car!”, Yes? The thought is the same, but pay attention - already six words instead of two. And finally, imagine how a person at the age of thirty will say the same: “Hey, look, this is the 2008 2008 Ferrari California with a 454 horsepower V8 engine and a 7-speed automatic transmission! It accelerates to a hundred in 3.9 seconds! ” Yes, there are more details here, but if you are not a car mechanic or a Ferrari fan, you probably don’t need them and don’t matter. The main idea is the same as in “Wow, see what a cool car!” Or “Dad, bl-r!”. But it is expressed already in 30 words.
Have you noticed how the abstraction "interesting car" has become overgrown with details and nuances, began to occupy much more space in the text and time for understanding, analysis and response? The same thing happens with software code.
What is it all about
In my opinion, the main characteristic of a good programmer is not deep knowledge of mathematics, not 100 years of experience behind him, not knowledge of a heap of languages ​​and libraries, not% heap_drug_effect_new_%, but the ability to see abstractions. Not only to see, of course, but also to design, use, correct, etc. But the fact is a fact. The success of some popular product today (substitute your favorite OS, browser, game - whatever) is determined by how well its architecture is designed, how well the high-level parts are separated from the low-level and from each other.
')
Look at the "dead" projects. Very rarely, they die from the fact that the programmer could not increase the speed of work by 10%, or because they could not fasten the necessary library. Most often, the reason for the closure is formulated in the spirit of "the existing architecture makes it impossible in principle to further develop." Here it is, a mistake in the vision of abstractions. Someone once did not see for a long time that several entities are actually one, or that one can have several representations, or that the client does not really have to pull the server, but vice versa, or that it would be nice to lay down the possibility of expanding the protocol - and here it is, the thunder of consequences that followed after years.
Patterns
In the modern world of programming there is such a thing as "patterns". Well, you know, the book of the gang of four, all kinds of factories there \ singltons \ wrappers \ observers \ facades \ bridges. The attitude of programmers to patterns is ambiguous. There is a camp of pattern lovers who rightly argue that this is all - the quintessence of decades of the best programming experience, proven things and should not be slowed down, but to use the achievements to the fullest. And there is a camp of opponents of patterns who blame them on excessive complexity (3-5 classes for the realization of one idea - quite typical for an average pattern), they say that studying patterns is like a school binge - when you just learn something without understanding causes, effects and options for precisely targeted use.
It seems to me that this is again a matter of patterns with abstractions. Some patterns are complete and unambiguously describe any one concept. It can be understood, seen, realized, encapsulated in itself. No matter, there will be one class, five or ten - if they form an entity that does not depend on the external environment, which can be placed in a separate module and then used through a simple interface - this is a good pattern.
Other patterns are overt trash. Just because these two classes are in the third, inherited from the fourth and call the methods of the fifth - they do not create an abstraction. Perhaps they somehow speed up the code or bypass some limitation of the language, but they do not form a holistic idea. It is difficult to understand, impossible to remember and it causes righteous anger in the rational brain of a programmer. Approximately as a random set of words, not forming a sentence - the reader. Pajamas sweet jump Moon contrary fast hatch?
Instruments
Unfortunately, modern development tools do not provide good automatic tools for seeing abstractions in a project. Yes, you can see interfaces or abstract classes in code - but not the fact that this is what constitutes a real abstraction. A certain layer of logic can contain dozens of interfaces, and the other can have just one class (and that one is just an empty wrapper around something else). We can use the IDE to see classes, methods, and variables — but we don’t see the real separation of the project into layers. Everything remains on the conscience of the programmer. Fortunately, today we have the opportunity to make the code in separate modules, we have namespaces, interfaces, good advice on refactoring and tools for its implementation. It is possible to write a code well divided into separate modules. And this is more important than writing fast code. Of course, the “modular code” is not 100% equal to the “ideal code”, but very, very close to this.
Bad code example
A few years ago there was a peak in the popularity of the text editor Notepad ++. Tens of millions of downloads, nice minimalistic interface, plugins. The beginning was very good, nothing foreshadowed trouble. Over the past couple of years, this text editor has been blown away and actually stalled in its development. Here is the schedule of its downloads.

What are the reasons? I do not undertake to call them all, but here is one. Let's look at one file from its source.
NppBigSwitch.cppCarefully analyze some parts of this code.
Misunderstanding of the term 'variable name'. The variable name is not the processor register name. The main task of a variable name is not to address a cell in memory, but to explain what data is in it. Simply for addressing, it would be possible to allocate an array in memory once and start to run on it with pointers. It is the variable name that is the abstraction that simplifies the understanding of the code. Here, as we see, does not simplify.
Misunderstanding of version control systems The old unnecessary code needs to be deleted with a comment in a separate commit about why it was deleted. If this is not yet added functionality - its place in a separate branch. Just to keep a bunch of commented-out code in project files means not to understand the ideas of version control systems.
Misunderstanding of where the program speed is more important, and where is the programmer’s speed LRESULT Notepad_plus::process(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) { ...
Nothing so much for one function, right? The author probably tried to save a whole ten nanoseconds to call each individual piece of code through a separate function. Well done, saved. And I received a code in which it is disgusting to dig and you need to spend a lot of time on understanding and correcting.
Misunderstanding of abstractions 'flag', 'constant', 'magic number'. nmdlg->Items[i] = 0xFFFFFFFF;
What is 0xFFFFFFFF, 1, 2 and 30? Oh yeah, 0xFFFFFFFF - means that the file has been closed. What a wonderful comment, how everything is vividly! It was apparently very quick and convenient to simply jot down the numbers into the code - it compiles after all. No time to explain, let's go further, right?
Lack of abstraction over encodings case COPYDATA_FILENAMESA : { char *fileNamesA = (char *)pCopyData->lpData; CmdLineParams & cmdLineParams = pNppParam->getCmdLineParams(); #ifdef UNICODE WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); const wchar_t *fileNamesW = wmc->char2wchar(fileNamesA, CP_ACP); loadCommandlineParams(fileNamesW, &cmdLineParams); #else loadCommandlineParams(fileNamesA, &cmdLineParams); #endif break; } case COPYDATA_FILENAMESW : { wchar_t *fileNamesW = (wchar_t *)pCopyData->lpData; CmdLineParams & cmdLineParams = pNppParam->getCmdLineParams(); #ifdef UNICODE loadCommandlineParams(fileNamesW, &cmdLineParams); #else WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); const char *fileNamesA = wmc->wchar2char(fileNamesW, CP_ACP); loadCommandlineParams(fileNamesA, &cmdLineParams); #endif break; } } return TRUE; }
In each place where a program is compiled in Unicode or not, we see constructs that load the programmer’s brain with things that are not needed at the moment. Even in the Win32 API, where many functions have unicode and non-unicode versions, we do not think what to call each time, we simply write “MessageBox”, and thanks to macros this is replaced with MessageBoxA or MessageBoxW. This allows you to rise above this level, disable the brain from having to remember about this part. For the authors of Notepad ++, this way seems to be too easy.
Lack of abstraction layer over operating system UI-primitives ::MoveWindow(_rebarTop.getHSelf(), 0, 0, rc.right, _rebarTop.getHeight(), TRUE); ... ::SendMessage(_statusBar.getHSelf(), WM_SIZE, wParam, lParam);
Every time we need to move a window, hide it, or perform some other action with an interface element, the Win32 API functions are pulled directly. No interface libraries, no wrappers, no classes. As a result, a lot of superfluous and duplicate code, absolute non-portability to other OS, all the disadvantages of the Win32 API are right inside our code. Moreover, the authors expose this approach as an advantage of the product, they say, no extra components! It's just awful. Hundredths of a percent increase in performance (at best) - and hell in the source.
As a result of all of the above, developing Notepad ++ is catastrophically difficult and slow. My patch that fixes a couple of important bugs for me has been on the “pending” list for six months now, along with almost 200 other patches. The author, of course, accepts some of them from time to time, but you yourself understand that it is absolutely impossible to do it quickly with such a code base. I am very sorry for the editor who once seemed to be a good editor, but you see yourself - death is inevitable.
Good code example
Perhaps you know such a popular library as Qt - it has been a lot written about it lately on Habré. Again, I don’t presume to say that I know best of all the reasons for its success, but here’s one of them. The entire library is built on a beautiful core of abstractions: there are abstractions from the platform, from the network, from the elements of the OS interface, from encodings, and from virtually anything. Looking at any Qt component, we don’t need to go deep down or up so that we understand how it works. And all this is not due to good documentation, but because of the code of the library itself.
Let's take a look at one of the Qt library header files.
qpdfwriter.h #ifndef QPDFWRITER_H #define QPDFWRITER_H #include <QtCore/qobject.h> #include <QtGui/qpagedpaintdevice.h> QT_BEGIN_NAMESPACE class QIODevice; class QPdfWriterPrivate; class Q_GUI_EXPORT QPdfWriter : public QObject, public QPagedPaintDevice { Q_OBJECT public: explicit QPdfWriter(const QString &filename); explicit QPdfWriter(QIODevice *device); ~QPdfWriter(); QString title() const; void setTitle(const QString &title); QString creator() const; void setCreator(const QString &creator); bool newPage(); void setPageSize(PageSize size); void setPageSizeMM(const QSizeF &size); void setMargins(const Margins &m); protected: QPaintEngine *paintEngine() const; int metric(PaintDeviceMetric id) const; private: Q_DISABLE_COPY(QPdfWriter) Q_DECLARE_PRIVATE(QPdfWriter) }; QT_END_NAMESPACE #endif
Do not be scared of possibly unfamiliar macros - not about them. I want to draw your attention to another thing. Note that there are no private properties in this class. And in almost no other Qt class, either. Rather, in fact, in each of them there is exactly one private variable — this is an implicitly declared via the Q_DECLARE_PRIVATE macro pointer to a subclass, which already contains all the properties and part of the private methods. This is done according to the official explanation to ensure binary compatibility - it turns out that this class in any version of Qt has the same size (since the storage space for one pointer is constant within the platform) and it can be safely transferred back and forth between modules without fear there is a segmentation fault.
In fact, for me, the beauty of this solution is different. See - we open the header file and what do we see? Only public methods. This is the first time you see this header file (and, in general, the Qt library, maybe) - but you already understand what this class is and how to use it, right? All entrails are gracefully hidden in a private subclass. Unfortunately, classic C ++ forces the programmer to mix in the header file what is needed by the external user of the class and its inside. But in Qt, when reading a header file, we see a clear message from Qt developers: “You don’t need the insides of this class. It's not your business what is there and how, this is an abstraction - use it through public methods. ” And that's great, damn it! I want to see all libraries in a similar style. Show me the things I need right away and hide unnecessary things so that they have to be searched for for a long time and hard. Behind this approach is the future, I am sure.
findings
Most good tips for programmers like “Use refactoring”, “Use good patterns”, “Premature optimization is evil”, “Do not write large functions”, “Don't start global variables” are actually conclusions from a more general advice “Know how to see abstractions ".
Have a nice programming.