📜 ⬆️ ⬇️

Programming is not a jenga

Do you feel like a player in Djengu (a game in which players alternately add one element to the structure to be constructed, loses one whose action will lead to its destruction) when you program? How easy is it to make changes to your software systems? Do you fear that after making changes your code will suddenly stop working? If you affirmatively answered one of these questions - this is a clear sign of poorly designed code that slows down your work the very next day after its appearance. Have you ever had to develop a code that was difficult to understand the very next day? I was fortunate enough to attend many presentations of Bob Martin (Uncle Bob) and I really like that part of his speech, where he asks the audience if they have experienced a significant slowdown in development speed due to bad code. When most of the listeners raised their hands, he asked: “So why did you write it that way?”.


image
Let's play Erudite?

This article is based on the results of the presentation of approaches to the development of extensible program code by Mark Nijhof and is translated by Alexei Smirnov with the permission and blessing of the author. It illustrates with examples using the principles of SOLID, an understanding of which will be useful both for beginners and experienced developers.
image
Looks more familiar?
')
In order to solve the problem of inflexible code, we need to change the style that we use to write it, we also need to think more about upcoming changes and write not only easily modified code, but also code that literally expects changes to be made in future.

Write the code as if you were going to change it tomorrow. Great help in writing such a flexible code is provided by patterns (templates) which include principles that help in its subsequent restructuring. In particular, these are the so-called SOLID principles. Let's try to figure out what it is. SOLID is an abbreviation that describes five different principles that are vital for structuring code that greatly improves your system’s ability to cope with future changes.
image
Great design is to hear the answer to a difficult question, and then think: Well, of course!

Michael Feathers, author of the famous book “Working Effectively with the Legacy Code” was one of the people who suggested the abbreviation SOLID. Let's look at what principles it consists of.
• S - Single Responsibility Principle (SRP)
• O - Open Closed Principle (OCP)
• L - Leskow Substitution Principle (LSP)
• I - Interface Segregation Principle (ISP)
• D - Dependency Inversion Principle (DIP)

Bob Martin is unwavering about the consistency of these principles, which are not reflected in the abbreviation SOLID itself, and suggests changing their sequence to SDOLI. We will follow his proposal.
image
Quote by Tim Barcz

image
This is my sequence
In confirmation of Bob’s proposal, I can say that I conducted several internal presentations and each time I had difficulty in explaining them. It seems to me that due to the fact that the SOLID principles are closely interconnected with each other and the SDOLI sequence will greatly simplify my narrative. So let's get started.
image
Pay attention to the wonderful tool that can do everything until one of its blades breaks, and then you have to throw it away, since one broken blade affects all the others. There must be no more than one reason for changing a class.

The Principle of Personal Responsibility (Single Responsibility Principle) is the first principle in SOLID and he informs us that: “There should not be more than one reason for a class change” . Understanding what a particular class or method is responsible for, and what is outside its area of ​​responsibility is always easier to modify. Having a personal area of ​​responsibility leaves you virtually no chance of making changes that may affect other parts of the system. Of course, you can change the behavior of the system, but not the code, and this greatly increases the possibility of its reuse. Moreover, software designs with a clearly defined personal responsibility area are easier to test in isolation from everything else.
image
Let's take a closer look at the various areas of responsibility of the OrderProcessor class. There are three of them here, related to processing the order, saving it and sending a confirmation message. Therefore, we have at least three reasons to modify this class.
image
As you can see, we have divided the areas of responsibility into three different classes. Now, a quick acquaintance with these classes is enough to understand what they are responsible for, which will not be superfluous when they are reused.
image
Not as painful as it looks. Not at all. Low-level module should not depend on low-level, both should depend on abstractions. Abstractions should not depend on the details. Implementation details must depend on abstractions.

Dependency Inversion Principle is the fifth and last SOLID principle, which states: “High-level modules should not depend on low-level modules, but both of these levels should depend on abstraction” and “Abstractions should not depend on details, details should depend on abstractions . Brrr. Simply put, this means that nothing in your code should depend on a specific implementation, only dependencies on interfaces or abstract classes are permissible.
The reason you must follow this principle is that it allows you to easily replace one implementation with another, without any consequences for your code. Yes exactly. Your code absolutely shouldn't care.
image
In this example, you may notice that my OrderProcessor class depends on the implementation details of Repository and MailSender. What if I have to change its implementation of sending notifications and use another class for this, for example SmsSender? The bad news is that due to the fact that the OrderProcessor class is endowed with knowledge of the way messages are sent, I will have to change its implementation.
image
Now OrderProcessor has no more dependencies on the method of implementation and depends only on the IRepository and IMailSender interfaces. Therefore, if I need to change the way the notification is sent, I will simply create a new implementation of the IMailSender interface and give it to the OrderProcessor. The OrderProcessor itself will continue to work in the same way as before, its implementation will not change and it will continue to care only about the logic of its behavior.
image
For a hairdresser, she is lost, but you can give her mascara or lipstick to make modifications possible.

Open - Closed Principle is the second in our list (Open Closed Principle), it states that: “Software entities should be open for expansion, but closed for modification” . This means that you should be able to change the external behavior and external dependencies of the class without its physical modification. You will need this behavior when you work within the framework of already defined interfaces and want to make certain changes to one of the parts of the code, leaving the other parts unchanged.
image
As you have probably noticed, this class is the result of using the Dependency Inversion Principle, it also follows the Open Closed Principle. Does this mean that the principle of opening closure can be achieved using Dependency Inversion? Of course, but this is not the only way to make a class extensible.
image
In the previous slide, I added the IOrderProcessor interface to the OrderProcessor class declaration, thanks to which I was able to create an alternative implementation of this class. Then I created a decorator class (Decorator, OOD template) DecoratedOrderProcessor, which adds additional behavior to my original class and eliminates the need to modify it. Using a similar approach, I can even add this behavior on the fly (at run-time). Another good example for demonstrating such an approach is the construction operating at FubuMVC .
image
Garbage truck does not care what color the container with garbage, while it opens from the top, not the bottom.

The third SOLID principle is the Leskow Substitution Principle, which tells us: “A function that uses a reference or pointer to a base class should be able to use an object or its derivatives without knowing the details of its implementation . ” And this means that when your code uses a certain class or interface, it should be able to use the derived class or alternative implementation of this interface without changing their internal implementation. This principle minimizes the impact that modifications have on your code.

image
Notice how the PriorityOrder class violates the Leskov Substitution Principle. When a code that uses an instance of the Order class checks whether the order status is valid (the IsValid function), it expects to get a result in the form of a Boolean value, but when an instance of the PriorityOrder class is used, it is necessary to further handle a potential exception that may occur when the order is condition. In order to work with these classes, the developer needs to know exactly which type he is dealing with: Order or PriorityOrder. And this, of course, violates the Leskow Substitution Principle.
image
This example uses Bob Martin. Notice that the Square class offers a completely different implementation of the Height definition from the Rectangle class, and therefore all Rectangle clients that expect that the area of ​​the shape (Surface) is calculated as a product of Width and Height, will get an unpleasant surprise.
image
Tell the doctor only what will help him to cure you, although depending on the doctor you may need to tell him all your life. Clients should not be dependent on interfaces that they do not use.

The Interface Segregation Principle is the fourth SOLID principle and it sounds like this: “Clients should not depend on interfaces that they do not use” . This means that when a single object has different uses, it should not confuse the different areas of responsibility of its customers, and therefore different interfaces are needed to separate the visibility of the various zones. I do not want to repeat, saying that it also minimizes the impact of changes on your code.
image
Imagine a simple online store where customers add products to their baskets, can view their contents, enter information about themselves and how they are delivered, and immediately before completing an order, receive summary data on the order to confirm it. So, we have three different ways to use an order, but not all of these uses need all the information that the Order class can provide.
image
In the example, you can see that we have created separate interfaces for the various steps of the order process. The Order class still looks the same, but now different clients have access only to those functions of the class that they need to perform their tasks. In other words, they see the Order class differently. This approach allows you to create and use different representations of the object for each of the steps, implementing only the interface you need in a given place.
image
Programmer magic hat

It is difficult to do Dependency Inversion (Dependency Inversion) without the Inversion of Control Container. The problem is that all your dependencies on a specific implementation at the level of the method implementation will move to a higher level of code. It is in this case that the Inversion of Control Container comes to our rescue.
image
Look at this code. As you can see, dependencies on a specific implementation are still alive at this level, brrr ...
image
It looks better, I would even say much better. I want to demonstrate to you in this example the use of the Common Service Locator, which has a common interface and supports all Inversion of Control Containers. The correct Inversion of Control Container (Inversion of Control Container) can automatically resolve the dependencies that the requested classes have and embed them in the newly created ones. In other words, it automatically does what I usually do manually.
image
Configuration details will depend on the Inversion of Control Container that you will use in your project. I, for example, use StructureMap . He is not bad because it is configured in one place of the code where all valid dependencies are defined, and this is the only place in the code where I know about various implementations. Many containers support configuration using XML, but I’m against using them, as this loses type safety and refactoring support.
image
Development with the help of tests (Test Driven Development / TDD and Behavior Driven Development / BDD) or Test Driven Design (Test Driven Design) are the techniques that force you to develop the right design. Writing tests before writing code really helps to pay more attention to the design and concentrate on what you are going to implement, so that the “lost” time on developing tests will pay off the best design that you get as a result. Tested code will also be easier to modify in the future. I see no excuse for not testing the code. Testing is used in many areas, why not use it when developing programs?
image
Don't Repeat Yourself. As I said before, if you have similar functionality, repeated several times in the code, you risk modifying it and forgetting to change something. And this applies not only to duplication of the system code, but also to tests. Duplication makes your code fragile. Do not repeat! Well, again, I did it ...
image
You do not need it. I will add a picture only when the requirements tell me to do it.

You won't need it (You Ain't Gonne Need It). This happens very often. You just work on some part of your system and think, “Oh! I came up with a piece that will certainly come in handy to me. I will add it now, because today is the easiest way to do this, because this is exactly the part of the system that I do today. ” Naturally, when you get closer to the moment when you really need this thing, you will have a much better understanding of what you really needed. And of course you will realize that this is far from what you wrote earlier. Thus, instead of saving time, you spend extra time doing your work. Do not forget that the code written for the future may also contain defects, it will need to be accompanied. Add functionality to a well-designed system is not difficult, so expect changes correctly!
image
Always write the code as if the guy who accompanies him is a frantic maniac
Be honest with yourself and start writing quality code today.

Alexey Smirnov is grateful to Sergey Slepchenko, 21csm, and to all who helped / helped with the translation and correction of inaccuracies in the article.

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


All Articles