Before I introduce myself and explain myself a little, I’ll probably write to whom this article is intended and devoted, so that the honorable reader could immediately drop it as useless.
So, to whom? First of all, probably the same as me - for beginners in the field of designing software systems. Those who do not have enormous empirical experience and own design patterns solely on the basis of general reasoning. Even more effective is the reading of such an article to those who have never heard of
SOLID ,
GRASP and other design principles. For I sincerely hope that I will be able to show how, based on the laws of logic, all those unshakable postulates that previously seemed
a priori true are derived from the basic theoretical judgments.
Nevertheless, in spite of such a low level, I would still wish that an experienced and experienced programmer, who has a strong empirical base, firmly entrenched in his neural networks, gave some constructive advice to modest beginnings.
Foreword
Why did you go on such a path and write about such a fundamental, most likely by all long understood, topic?
')
A few reasons.
First of all, I was always inspired by Richard Feynman (I know that neither the first nor the last one) is the greatest man who has a not infectious halo of inquisitiveness and desire to penetrate the very depths of things. His fearlessness in the face of ignorance cannot remain indifferent, and therefore one wants to challenge the depths of the unknown again and again.
Secondly, I do not cease to admire mathematics, in particular, that many of its ideas and concepts follow from one another, based on the smallest axioms. I am a supporter of the view that the entire mathematical world acquires its existence immediately, as a person agrees with the fundamental rules, and all that remains for him then is to reap the fruits of his own work with the help of persistence and speculation.
Perhaps the most basic motive is in everyday work, sorting out about how and where to apply basic design patterns and principles, I still lack depth, the ability to quantify and formally evaluate how good a particular code is from a design point of view. I am sincerely convinced that code is not art, it is strict, analyzable structures, and I do not see it as effective to focus on the reflexive sensations of “
beauty ”, when there is certainly an opportunity to adopt something more powerful and rational.
Who am I? My name is Josh, I'm from Kharkov, I'm 22, and I'm still
Junior Software Developer. Probably. About a year ago, I already
published on Habré, and at that time my thoughts on the component-oriented C # engine were not as badly met as I had expected. Moreover, the publication broke out of the sandbox and typed views for a while. But this is so, dating for the sake of which I already dragged.
I will begin a brief description of the structure of the narrative. In this article I will try to put forward judgments in a thesis and consistently, like Ludwig Wittgenstein in his
Logical and Philosophical Treatise (not so good, really), dressing them in the form of a chain, each subsequent link of which is necessary and necessarily based on the previous one.
The fabric of the work will consist of several parts, the first of which is a set of provisions and concepts, sometimes taking the form of axioms. This foundation can be understood as local rules and laws, according to which the theory is developed.
In the second part, I will show how principles and laws (PLO, SOLID) follow naturally from these developments, as if they always existed there, precisely associativity and commutativity in algebraic rings, which seem to always exist from that moment on. as we agree with the concepts of addition and multiplication.
Well, I hope, I did not tire the reader with such a long introduction, and, perhaps, let's get started.
Position 1. Code is written by people.
Note: If you are reading this article at a time when this axiom is not such a long time ago, I hurry to inform you that, first, do not exceed the speed of light while intoxicated, because this can lead to time turning reversed, and you will again become sober, and, secondly, close this article, because at this time it appears in the search engines solely because of a joke.Structure
Metaphor 1 . Hierarchically everything. The Universe can be viewed as a set of levels of varying degrees of approximation: quarks, atoms, molecules, substances, cells, tissues ... It’s no secret that human thinking itself takes on a hierarchical form when lower-level modules add top-level modules, and therefore the question is whether The universe or, after all, man, I will leave to the inquisitive reader as a philosophical exercise, for such reflections do not concern the main line of the plot.
The concept of 1.1. Task - the requirement for the functionality of the application.
The concept of 1.2. A block is a code centered around the execution of one and only one task.
The concept of 1.3. Dependency - the use of one code block of another.
Concept 1.4. The degree of approximation is the number of levels that must be climbed from the atomic level in order to achieve this.
The concept of 1.5. Abstraction is a block that does not have a specific implementation at the compilation stage.
Function 1.1. Apr (x) is the degree of approximation of x.
Function 1.2. Qd (x) - the number of dependencies of the block x.
Regulation 1.1. Over time, the number of blocks that make up the software system increases.
Regulation 1.2. Atomic for a software system is the level of basic operators and keywords.
Regulation 1.3. The higher the degree of approximation of abstraction, i.e. the more general a task it is called upon to solve, the less the likelihood that changes will appear.
Regulation 1.3.1. Dependence on abstraction is less likely to lead to indirect changes.
Regulation 1.3.1.1. Abstractions reduce entropy.
Regulation 1.4. Redundancy begets changes.
Processes
Concept 2.1. Creation - an increase in the number of blocks in the application by writing a new code.
The concept of 2.2. Change - displays the change in task formulation on blocks.
The concept of 2.3. Indirect change - display of block changes on dependent ones.
Concept 2.4. Correctness is a quantitative characteristic of verification. It shows how accurately and fully the block works with respect to the pre- and post-conditions put forward.
The concept of 2.5. Entropy is a quantitative characteristic of code quality that shows how much additional budget will be required to implement a new functionality. It is expressed through the relationship between the average change time and the average block creation time.
Function 2.1. Tc (x, y) is the time to create a block x within the framework of task y.
Function 2.2. Tu (x, y) is the time of changing the block x within the framework of the task y.
Function 2.3. Qu (x, y) - the number of changes of block x within the framework of task y.
Function 2.4. Qm (x, y) - the number of braids. block x changes as part of task y.
Function 2.5. Md (x) is a mapping from the set of indirect changes of the block x to the set of those that will lead to real ones.
Function 2.6. Cor (x) - shows the degree of correctness of the block x, i.e. the relationship between the theoretical result and the actual. It can be formally defined as the ratio of the number of elements of a set, which is formed by intersecting the results of the expected function with the actual, to the number of elements of the set of results of the expected function.
Function 2.7. Ku (x) is the fragility coefficient of block x. The ratio between the number of Md (x) to the number of indirect changes x.
Regulation 2.1. New code increases entropy.
Regulation 2.2. Changes increase the entropy.
Regulation 2.3. Indirect changes indirectly reduce correctness.
Regulation 2.3.1. Indirect changes may lead to non-indirect.
Regulation 2.3.1.1. Indirect changes indirectly increase entropy.
Regulation 2.3.2. Test blocks reduce the degree of influence of indirect changes on correctness.
Organization
Metaphor 2. The universe managed to mysteriously defeat Nothing and create Something, acting on a surprisingly simple scheme: it determined the basic components of being and the laws by which they interact with each other, and now I have to sit on a cold winter evening to write about it.
The concept of 3.1. Reuse - using the same block to solve the same problem in all places of the application.
Concept 3.2. Block polymorphism - the ability to substitute an implementation block into an abstraction.
The concept of 3.3. Inheritance is the reuse of the parent's structure by a child block.
The concept of 3.4. Encapsulation - hiding the internal structure of the block from the blocks that use it.
Regulation 3.1. Reuse as reduces entropy due to the fact that it reduces the number of changes, and increases the number of indirect changes, as a result of which the entropy increases.
Regulation 3.2. Reuse reduces the amount of code.
Regulation 3.3. Polymorphism, inheritance and encapsulation allow the use of abstractions.
Regulation 3.4. Inheritance enhances reuse.
SOLID
Finally, I want to take the liberty of theoretically justifying the application of the most popular principles -
SOLID .
Single Responsibility Principle - the principle of one responsibility. Formally, the program module or class should only have responsibility for only one functional part provided by the application. He should have “
only one reason for change ” (Robert Martin). In our theoretical model, this directly follows from the definition of dependency: as already shown above, a state where logically one top-level block contains two lower-level blocks that perform different tasks, but are dependent on each other, has a greater entropy than a state without cyclic dependence .
Open / Closed Principle - the principle of openness / closeness. Briefly: changing the behavior of an entity should not be done by modifying its source code, but by expanding, which means specific mechanisms such as inheritance, polymorphism, and abstractions. In the light of the above constructions, it can be said that the time of introduction of new functionality is only the time of creation and not the time of change, which thus significantly reduces the entropy.
Liskov Substitution Principle - Barbara Liskov substitution principle. He says that it should be possible to replace all objects of type
T with objects of type
S , where
S is a subtype of
T , without prejudice to the correctness and efficiency of the program. In formal language, this can be expressed as: let the function
f (x) be true for all
x of type
T , then the function
f (y) should also be fair for all
y of type
S , where
S is a subtype of
T. This principle is a consequence of the desire to reduce the fragility of the application and, unfortunately, does not give any specific recommendations, but only postulates a requirement for a software system.
Interface Segregation Principle - the principle of separation of interfaces. Announces that a large number of small interfaces are better than one large, because Interface-dependent clients can only use the part they need.
I can see that this principle is a continuation of the
SRP applied to the domain of abstractions. Arguments and proofs are absolutely the same.
Dependency Inversion Principle - the principle of dependency inversion. For me, one of the most difficult to understand principles is that high-level objects should not depend on low-level objects, and vice versa - both levels should depend on abstractions. The consistency and truth of this principle directly follows from
Provision 1.3.1.1 : the probability of changing abstractions is lower than the probability of changing a particular implementation unit.
Considering the notorious
SOLID principles, I pursued one goal: to show that they are only generalizations and names for some strategies and ways to reduce the entropy of an application, be it by reducing the number of dependencies or decreasing the probability of a particular block changing.
I propose to do away with this theory. A lot of dry and, I think, quite obvious information was offered to the reader’s attention. This part, I repeat, must be presented as an axiomatic basis, although most of the provisions can still be formally proved with proper skill, based on axioms of a lower level, say, the law of addition of probabilities and some others.
Practice
As an exercise, consider a real-life example and try to quantify its structure.
DamageMediator - the simplest class, which is a component of the damage mediator. His task is to absorb the cunning interaction between the various components of the parent container in order to calculate the damage of the character taking into account equipment, weapons, characteristics and other things.
public class DamageMediator : GameComponent
{
public int Next()
{
var equipment = GetEquipment();
var stats = GetStats();
var weapon = equipment.Weapon;
var damage = weapon.Damage;
var isCrit = stats.CriticalChance.Next();
var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
return result;
}
}
.
-:
GetEquipment,
GetStats,
Equipment.Weapon,
Weapon.Damage,
Stats.CriticalChance,
CriticalChance.Next,
Damage.Next.
-:
Equipment,
Stats,
Weapon,
Damage,
Chance.
Qd = 12.
, , .
1.
GetEquipment GetStats, .
public class DamageMediator : GameComponent
{
public int Next(Equipment equipment, Stats stats)
{
var weapon = equipment.Weapon;
var damage = weapon.Damage;
var isCrit = stats.CriticalChance.Next();
var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
return result;
}
}
2.
Equipment Weapon.
public class DamageMediator : GameComponent
{
public int Next(Weapon weapon, Stats stats)
{
var damage = weapon.Damage;
var isCrit = stats.CriticalChance.Next();
var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
return result;
}
}
3.
Weapon Damage.
public class DamageMediator : GameComponent
{
public int Next(Damage damage, Stats stats)
{
var isCrit = stats.CriticalChance.Next();
var result = isCrit ? damage.Next() + damage.Next() : damage.Next();
return result;
}
}
4.
Stats Chance.
public class DamageMediator : GameComponent
{
public int Next(Damage damage, Chance criticalChance)
{
var isCrit = criticalChance.Next();
return isCrit ? damage.Next() + damage.Next() : damage.Next();
}
}
:
Qd = 4.
, , , , . , .
-, , , , :
GetEquipment GetStats. — . , , :
DamageMediator GameComponent GameComponentContainer ( , , ), , , .
-, . , ( , ) : , , , ?
, , , , , , , , , .
, , , , , , : , , . , .
, , , , : “
, , ? ”.
,
DamageMediator: , (, , ), , , .
, , ( , ), .
, - . , , , , , — .
, , , .
, Next, . , . :
public class DamageMediator : GameComponent
{
public int Next()
{
var equipment = GetEquipment();
var stats = GetStats();
return Next(equipment.Weapon.Damage, stats.CriticalChance);
}
private static int Next(Damage damage, Chance criticalChance)
{
var isCrit = criticalChance.Next();
return isCrit ? damage.Next() + damage.Next() : damage.Next();
}
}
, , ? , , : , . , , .. , ,
Qd = 4.
, , .
, .. , :
public class DamageMediator : GameComponent
{
public int Next()
{
var equipment = GetEquipment();
var stats = GetStats();
return DamageUtil.Next(equipment.Weapon.Damage, stats.CriticalChance);
}
}
, , , , . , , .
, , . , , , , .
, : , .. , ; , .. , ; , .. .
, , , , , .
, , , , , , , .. , , , .
, , - , , , , .
, , : , , , , , , .. , , . , , .. , , , , .
, , , , , — , , , , .
, . , , .
!