Sustainable software architecture
Over time, maintenance often changes from structured programming to defensive programming. In order to counteract this downward spiral in the long run, we need a high-quality and flexible architecture with as little technical debt as possible.
Almost every software system is developed with good intentions but under difficult conditions. Developers have to struggle with deadlines that force them to program hacks; there are different qualifications in the development team that lead to code shares with different qualities; and in addition, the team has to take care of old code that is messy and interwoven into a big ball of mud.
Over time, maintenance is changing more and more from structured programming to defensive programming. Developers start writing code that they know is bad from an architectural point of view. The code becomes more complex and the team accumulates technical debt. Maintenance costs increase and enhancements lead to more side effects. The result is poor architecture that drives up development costs.
In order to counteract this downward spiral in the long run, we need a high-quality, flexible architecture with as little technical debt as possible. If the technical debt is low, the maintenance developers will find their way around the system easily. They can fix bugs quickly and easily and have no problems making cost-effective extensions. How do we get into this promised land of architectures with reduced technical debt?
The term technical debt was coined by Ward Cunningham in 1992 . Technical debt arises when consciously or unconsciously wrong or sub-optimal technical decisions are made. These wrong or sub-optimal decisions later lead to additional costs, making maintenance and expansion more expensive. At the time of the wrong or sub-optimal decision one has taken up technical debt, which one must pay off with interest at some point, if one does not want to end up over-indebted.
The literature lists various types and variants of technical debts. This article focuses on the technical debts that slow software developers down in their work:
- Implementation debt: The source code contains so-called code smells, such as long methods, empty catch blocks, etc., which can be used to implement the code. Today, implementation debt can be found largely automated with a number of tools in the source code. Every development team should successively eliminate those debt in its daily work, without the extra budget being required.
- Design and architecture debt: The design of classes, packages, subsystems, layers and modules and the dependencies between them are inconsistent, complex and do not fit with the planned architecture. This debt cannot be determined by simple counting and measuring and requires a comprehensive analysis, which is presented in Section 3.
Other problem areas, which can also be regarded as a debt of software projects, such as missing documentation, low test coverage, poor usability or insufficient hardware, are left out here.
The origin of technical debt
If a high-quality architecture was designed at the beginning of a software development project, then one can assume that the software system is easy to maintain at the beginning. In this initial stage, the software system is in the corridor of low technical debt with constant maintenance effort (Fig. 1).
If the system is expanded more and more, technical debt inevitably arises (yellow arrows in Figure 1 in the corridor of constant costs). Software development is a continuous learning process in which the first throw of a solution is rarely the final one. The revision of the architecture (architecture renewal, green arrows in Figure 1) must be carried out at regular intervals. This results in a constant sequence of expansion and refactoring.
If a team can follow this cycle of expansion and refactoring permanently, the system will remain in the low technical debt corridor (yellow and green arrows in Figure 1 in the maintenance corridor). If the development team is not allowed to continuously reduce the technical debt, architectural erosion will inevitably occur over time (yellow and red ascending arrows in Figure 1).
Once technical debts are accumulated, software maintenance and upgrades become more and more expensive, and consequential errors are more and more difficult to track, to the point where every change becomes a painful effort. Figure 1 illustrates this slow decay as the red arrows become shorter and shorter. With increasing debt, less and less functionality can be implemented per time unit.
In order not to get into this dilemma in the first place, we need on the one hand the knowledge in the development team about what constitutes a high-quality and flexible architecture, and on the other hand methods in the development, which contribute to the preservation of this architecture.
Cognitive psychology as the basis of sustainable software architecture
In the course of evolution, the human brain has acquired some impressive mechanisms that help us deal with complex structures. These mechanisms must be used in software systems so that maintenance and expansion can be carried out quickly and without many errors. Our goal is to be able to develop our software systems for a long time while maintaining the same quality, even with changing development teams. The three mechanisms that our brain has developed for complex structures are (Fig. 2): chunking, formation of hierarchies and structure of schemata. These mechanisms have direct images in criteria for architecture.
If these mechanisms and their implementation in the architecture are clear to the development team, an important basis for a high-quality architecture is laid. Further details on the implementation of modularity, hierarchization, and pattern consistency can be found in my book “Sustainable Software Architecture, Analyze and Reduce Technical Debt” published by d.punkt-Verlag .
Mob architecting as a method for architecture quality
In order to keep the architecture at this high level, the development team must continuously work on improving the architecture. A good way to support this work on the architecture is mob architecting. With mob architecting, it is possible to directly check on the source code to what extent the planned architecture has been implemented in the source code (Fig. 3) and how high the level of training of the software is. The target architecture is the plan for the architecture that exists on paper or in the minds of the architect and developer. Several good tools are now available for such target/actual comparisons: Lattix, Sotograph/SotoArc, Sonargraph, Structure101 and some more.
Figure 4 shows the process of a mob architecting. Mob architecting is carried out by a pilot together with all architects and developers of the system in a workshop. At the beginning of the workshop, the source code of the system is parsed with the analysis tool (1) and the actual architecture is captured.
This makes technical debts visible and the pilot, together with the development team, sets out in search of simple solutions for how the actual architecture can be adapted to the target architecture by refactoring (3). Or the pilot and development team may find out during the discussion that the solution chosen in the source code is better than the original plan. Sometimes, however, neither the target architecture nor the deviating actual architecture is the best solution, and the pilot and development team must jointly design a new target image for the architecture.
When a system is facing a major enlargement, this is the case. The planned architecture was not designed for the upcoming expansion, so that a fundamental further development of the architecture is necessary. Questions such as how to do this will be answered: Is the existing architecture flexible enough for the extension? Does the coupling need to be reduced in some places to allow restructuring? Is the cut of the modules and layers chosen correctly in order to integrate the new modules consistently with the new functionality? If you have adopted a software system that you have not co-developed yourself, then an in-depth architectural analysis is worthwhile in order to get to know the system in the first place. If the source code basis is unknown to the development team, the originally planned structures can only be made visible in this way.
In the course of such an architectural analysis, the pilot and the development team collect technical debt and possible refactorings (4). Together, the refactorings are prioritized and their implementation planned.
In this article I described very briefly what a development team needs to know and be able to do in order to keep its architecture in good condition. On the one hand, the development team needs to know what a modular, hierarchical and pattern-consistent architecture is. These basic concepts are poured into a plan – the target architecture – and finally into the source code. In the course of development and maintenance, the development team needs to be able to use mob architecting to check differences between the target and actual architecture and, if necessary, work out refactorings. If these two aspects are taken to heart and the team is provided with the necessary resources, every software system will remain sustainable.
 Ward Cunningham: “The WyCash Portfolio Management System”; Experience Report, OOPSLA ’92, 1992
 Carola Lilienthal: “Sustainable Software Architecture. Analyze and Reduce Technical Debt”; Dpunkt.verlag, 2019