Most developers even with little experience are faced with the problems of the design of their solution, when the task set can be solved in a dozen different ways and you need to choose a specific, more or less adequate option.
Attitude to the design (design) stage can be very different, starting from the approach taken at the early stages of the development of XP methodology, when it was thought that design and architecture are dinosaurs that have no place in the dynamically developing world of agile development. Many people still do not think about the design of the solution, considering that the iterative development process + refactoring will do everything for us and a good design will appear by itself.
There is another extreme when the team can spend weeks searching for the perfect solution (the Holy Grail of the architect), when the design will be able to “expand” in all possible directions, and be so “flexible” that it will not be possible to realize it.
')
Each of these extremes is curious to watch, but only if they do not occur in your team. In most cases, a reasonable attitude to design is somewhere in the middle, when the stages of design and development are closely linked and iteratively follow one after the other almost continuously. In this case, each time a developer is faced with the adoption of a decision, he tries to find a compromise among the infinite number of requirements that put pressure on him: use a more efficient solution, or a more extensible one; more importantly, consistency or the presence of breaking changes; how to be, break SRP (Single Responsibility Principle) or make the module more convenient to use; is it worth sacrificing code maintenance for efficiency, etc.
As a result, most developers come to the understanding that ...
Design is the art of finding a compromise among many conflicting goals and requirements, the importance of which can change over time.
Context is important
I’m somewhat strained by the categorical nature of many authors of books / articles or just colleagues who express their opinions in absolute form: never use singletones, test coverage should be 100%, open fields are a worldwide evil. The problem with such statements is that they are quite correct in most cases, but this does not mean that these tips should be blindly believed without thinking.
Yes, in most cases, open fields are indeed a dangerous practice, but who prevents us from using them in structures (meaningful types of .NET) to interact with existing systems? Yes, unit tests are a great thing, but this does not mean that without a 100% test coverage your project will fail. It is for this reason that when an experienced programmer is asked the question “What is better?”, Then “wise pepper” will not answer it head-on, but ask a clarifying question in order to understand the context of the problem being solved? After all, the fact that it is reasonable to apply in one case (writing unit tests for the production of code) may be completely unnecessary in the other (what if we are talking about a one-day prototype?).
For the same reason, we very often condemn the decisions of others and the quality of this decision is often measured by the number of WTFs per second; but the fact is that often we simply do not have enough information about the conditions under which it was accepted. Here is an example: in C # there is an extra doubtful possibility, called covariance of arrays. This means that the following code snippet is correct and will lead to a runtime error, not a compilation:
object[] o = new string[] {“1”,”2”, “3”}; o[0] = 42;
The reason for this possibility is related to the fact that it was from the first version in the Java language, and when developing the C # language in the late 90s, the importance of “hooking” existing programmers into the new language was decisive. That is why the familiar C-like syntax is used, which was already familiar to C / C ++ and Java programmers, even if it has its drawbacks. Such decisions may seem dubious now, but an understanding of the reasons for their adoption (consistency with other languages ​​vs the possibility of run-time errors) makes it clear why languages ​​or libraries are implemented so and not otherwise, and what influences their development.

You can not judge someone else's decision in absolute categories, in
order to properly evaluate the decision made, you need to understand under the pressure of what restrictions it was made.Well, you said? !!
How many times have you been told: “Hey, well, you yourself said that you shouldn’t do that, but you yourself do it!” Or “Well, damn, you give, we have already discussed everything with you, and now you say you need to to another! The point is that our decisions change over time. Over time, the importance of some criteria (code efficiency) may decrease, and the importance of other criteria (consistency of architecture and simplicity of maintenance) may increase.
I will never defend my decision to the end, just to prove something to someone, my attitude changes when new facts appear or the weight of existing criteria changes. This happens all the time when requirements are clarified or when it becomes obvious that in this particular case, performance is more important than attendance or usability can be sacrificed, since the number of customers will be limited. And the decision itself can influence the task so much that the original decision will change beyond recognition.
Good design
Determining the quality characteristics of a good design is difficult, as the path that a developer must go in order to achieve it is difficult. Most of us see problems in design perfectly, especially if someone else is the author of this design. Identifying problems in your own design is more difficult, primarily because the whole solution lies in your head, and every aspect of the design seems obvious.
A great way to find design problems is to look at it from the side, or try to explain it to someone.
A good design is a design that you can explain to your colleague in 10 minutes without sacrificing completeness or accuracy. If, in explaining “how it works,” you have to take into account many factors, dig into many details, and go back to the same thing 15 times, then there is clearly something wrong with the design. Good design is often quite simple, with a minimum of intricacies, and a minimum of unnecessary or unobvious links. A good design, like a
good architecture , struggles with the inherent complexity, and does not introduce additional complexity, which is already abundantly enough in the very nature of the problem to be solved.
The very degree of formalism and quality of design is also a compromise between the cost of a mistake and the cost of correcting it. If the interface between the two modules is more or less defined, then it is quite possible to move on to their implementation, and not to bring this interface to the ideal, fearing that if it is changed, it will be necessary to correct as many as 5 lines of existing code for two developers who sit at adjacent tables.
Good design is not an end in itself, so no need to strive for the perfect solution. As in many other things, common sense and pragmatism are your best advisers.
-
The design definition given in the middle of a note is a free translation (with some addition) of the thought of Eric Lippert, which he expressed in one of his
posts :
Design among the
various incompatible design goals.