Whether you are a simple programmer, a mother lead, an architect, or even a PM, you must have faced a difficult choice when adding a new feature to the system. One solution is much easier to implement in a short time and catch the next very important release, but it will be more expensive to maintain, less expandable or less reliable. Another solution may not have all these shortcomings, but it may have another, in some cases more important shortcoming - it will take considerably longer to implement it.
At the same time, the most difficult thing in choosing one or another decision is the “communication” of his choice to the immediate supervisor so that he can make an informed decision. And since from the point of view of most managers, the “weighting” ends immediately after he hears the implementation deadlines, the “communication” ends approximately 37 seconds after it starts (usually this is how much time the manager needs to know the answer to a very simple question expressed in one word: "When?")
It is not surprising that many simple programmers, hard leads and architects, and sometimes even PMs who understand that they themselves will have to deal with the problems of “myopic” solutions, do not agree with this approach. And it is not at all surprising that other well-known and not-so-many people who came up with typical “patterns” describing a similar situation faced a similar problem. One such pattern is the technical debt metaphor, first described by Ward Cunningham (*) nearly twenty years ago.
Technical duty in the classic sense
')
In the classical sense, i.e. In the form in which this metaphor was described by Ward Cunningham, technical debt refers to a deliberate compromise solution when the customer and key developers clearly understand all the advantages of a quick, if not ideal, technical solution for which they have to pay later. And although from the point of view of many developers, a situation where a bad decision may be good may seem insane, in fact, it is quite possible: if a short-term solution allows a company to get visible benefits, release a product before competitors, satisfy a key customer or some other competitive advantage, then such a decision is completely justified. Sometimes this may be the only way for a long-term perspective to exist at all.
A parallel can be drawn between technical debt and financial debt. Financial debt means that you receive additional funds now, but every month (or every half a year) you will have to pay a fixed interest rate, and at the end of the term you must return the entire debt to the lender. A similar situation occurs in the case of adopting a non-optimal technical solution: you received a ready-made system or a new opportunity today, but when you add a new opportunity, you have to pay "interest" or pay off your debt by refactoring the system or part of the system.
This metaphor has one very important feature: when it comes to a non-optimal solution, we are talking about using .NET Remoting instead of WCF, using Sybase instead of SQL Server, using DataSets instead of Entity Framework (**), but nobody talks about Crutching, dirty hacks, bad code, coherent architecture, and the like. On the contrary, the non-optimality of a strategic decision does not mean that it should be taken carelessly, to extend the influence of this decision to the whole system, or simply to r # $ nokodit. Quite the contrary, in most cases this means that this solution must be hidden in the form of implementation details, one or at least as few modules as possible, so that it can be changed later.
It is this approach to building applications that is optimal from an architectural point of view. If you do not feel a compromise between short-term and long-term benefits when making an architectural decision, then in any case you should not cut it into stone. It is possible that you made the wrong decision unconsciously, but because of your lack of understanding of the subject area, or because of the future change in the requirements of the customer. It is much easier to reduce the cost of future changes by encapsulating important decisions in the smallest number of modules, rather than trying to think through the architecture to the smallest details, hoping to take into account all possible and impossible scenarios. For a certain range of tasks, an ideal well-thought-out architecture is possible, and sometimes just necessary, but in most cases this is not the case; Sooner or later, the moment will come when your vision of the system or the vision of the system by the customer will change, which will require significant changes.
However, even with a reasonable accumulation of technical debt, there is a high probability that the team (or customers) will go according to the old principle - it works - do not touch it, and never return to this decision again to pay for their technical debt. In addition, there are many cases where technical debt accumulates gradually and unconsciously, and if developers don’t think about it, they will come to the situation as old as the world, when the cost of adding a new opportunity becomes incredibly expensive, everyone works, gets tired, angry at each other without gaining any competitive advantage. This leads us to the second source of technical debt - dirty code (***)
Dirty code as a source of technical debt
Technical debt in the classical sense is deliberate and mainly concerns strategic decisions, therefore the responsibility for it rests with the customers of hard leads, architects and even PMs, but for the most part simple developers are concerned with dirty code. In fact, the difference between a dirty code and a non-optimal strategic decision, in fact, is not so big: when you add a new feature to the system, you have to pay for short-sightedness in the past.
During coding, as well as at the time of making any other decisions, the developer must consider short-term and long-term benefits. Let's look at unit tests. If you ask the TDD adept about the need for unit tests, he will say: “r @ # but a question, unit tests should be for each class, module or function, and, of course, they should be written before writing the code”. However, if you listen to Kent Beck (****), the author of TDD, then his attitude to unit tests is more pragmatic. When deciding whether to use TDD or serious code coverage with unit tests, you also need to take into account short-term and long-term benefits. Of course, unit tests are very useful, but they are useful, above all, in the long term, and what if you realize that there is a high probability that these long-term prospects will not be at all? You can develop a prototype or something like proof of concepts, and are trying to figure out whether this solution will work at all or not. With a similar situation of inefficiency unit tests can be encountered in many real-world applications, when adding a new feature, the cost of writing a unit test can be several times higher than the cost of the implementation itself.
However, this is the exception rather than the typical situation. Usually, in the development process of an application, there is accumulated as a load of major strategic mistakes or misunderstandings of requirements, as well as a mass of minor tactical errors, such as long functions with unobvious responsibilities and complex side effects; vague classes, with fuzzy boundaries and responsibilities; lack of idioms of naming and documentation, etc ... All this leads to the classic
broken window syndrome , when no one has an idea to act differently, and who does, then it quickly disappears due to the fact that it is against the same course very very difficult.
And if the team does not pay its debts by rethinking existing solutions and their subsequent refactoring, if it does not try to keep the quality of the code at a high level, then sooner or later it will increase the cost of developing and maintaining the code so that all existing funds will go to pay interest on technical debt. That in turn will lead to low productivity of developers (still, because they only do that they are fighting windmills), and mutual dissatisfaction of the development team and the customer.
findings
Measuring the productivity of a programmer or a team of programmers is difficult, and it is because of this that many difficulties arise when choosing one technical solution or another: managers or customers simply do not understand what consequences they are waiting for when choosing one and rejecting another solution. The use of the technical debt metaphor is not a panacea and is unlikely to provide a decisive advantage in choosing one approach or another, but at a minimum, it will allow the key interested people to understand the problem in terms accessible to the mere mortal, and show the problem, albeit in abstract, but still financial units.
In addition, even if you have to compromise and get into a debt trap consciously or unconsciously, try to design your systems so that the influence of ambiguous decisions is minimal and the quality of the implementation code is high.
-------------------------------------------
(*) Ward Cunningham is a famous guy who made an incredible contribution to the development of the computer community; he is the “daddy” of the wiki, as well as one of the authors of the “patterns” and “extreme programming”. Information about the first contribution can be found in Wikipedia, and about the second - in the article
“Design Patterns. A success story .
(**) Of course, we are talking only about those cases where each of the above solutions seems more optimal, but the cost of its use right now seems unjustifiably high.
(***) Some authors, including Bob Martin, do not consider messy code a technical duty, but such a code increases the cost of adding a new feature, so it seems to me that it can also be considered as one of the types of technical debt.
(****) This is a podcast of
Software Engineering Radio Episode 167: Kent Beck , in which Kent Beck expressed this cherished idea.