1

Drift into debt

 1 year ago
source link: https://einarwh.wordpress.com/2023/04/25/drift-into-debt/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Drift into debt

Posted: April 25, 2023 | Author: Einar | Filed under: Uncategorized |Leave a comment

Kent Beck recently published a blog post called “Friction >> Debt”, where he suggests that the “technical debt” metaphor has outlived its usefulness and suggests “friction” as a replacement. The problem, according to Beck, is that “the business folks” are prone to interpret “technical debt” as a weak excuse for wanting to spend time fixing stupid mistakes that could have been avoided in the first place. Why would they want to pay for that?

This is certainly a problem. I’m not sure how to react to the fact that even Kent Beck still finds himself in the situation where he needs to explain to the business people that software development by its very nature involves spending some time and effort on development activities that don’t directly result in new or changed features. Is it heartening (same boat)? Or depressing (it’s sinking)?

Anyway, I’m not optimistic that switching metaphors will help. The reason is that it’s not just a matter of communication across the business/IT divide. What is lacking is a better understanding of the dynamics that produce the situation we – both developers and business people – invariably find ourselves in: that our joint venture, the software we build, has accumulated so much “debt” or “friction” or “cruft” or “dirty dishes” or whatever we want to call it, that our ability to make the software more valuable for end users has degraded to the point where it can no longer be ignored. I believe this understanding is missing not just from the business people, but us software developers as well. We might observe the symptoms and consequences of this process more directly, but I’m not convinced we understand the forces that produce them very well.

When we speak of “change” in software, we tend to think about changes in functionality: new features, extensions, refinements; in short, things that end users are meant to appreciate, and that the people footing the bill are relatively happy to pay for. But as we know, programs are hit by many other changes as well. Frameworks and libraries constantly evolve, some with breaking changes. Security vulnerabilities pop up and must be addressed. The license costs of a service provider increases by an order of magnitude and a replacement must be found. Old cloud offerings become obsolete and are eventually discontinued, while new ones appear. I’m sure you can think of many more. It is a constant push-stream of changes. We might not even notice all of them immediately, but they are still there.

When something changes that affects the software in some way, it produces a gap or a delta. This goes for all kinds of changes. Suddenly, there is a difference between the software as it is and the software as it should be. There may even be a difference between our current mental model of the problem domain and one that is suitable to support the change. These differences represent outstanding work. Actions are required if the gaps are to be closed. Since programs are dead and inert by themselves, it falls upon us humans to perform these actions.

Unfortunately, the process of gap-closing is riddled with problems. A change must first be noticed and reasonably understood, and then the corresponding gap must be addressed in a meaningful manner. To figure out the most appropriate response to a change may require deep understanding of many things: the problem domain, the mental model of that domain and the conceptual solution to the problem, the actual code itself, the architecture of the system, the programming models and technologies involved, the non-functional properties of the system, its dependencies on other systems, the runtime environment, and so forth. This is necessary to identify where and how the change affects the software, what parts need to be changed, to what extent the change is compatible with the assumptions the current solution builds upon, how it will interact with existing features, etc etc.

In the best case, it is obvious what we need to do to close the gap, and there will be no residues. For instance, we may be able to fix the gap caused by the discovery of a security vulnerability in a library by bumping the library version. But often we are not so lucky. In fact, it might not be immediately obvious what the proper response should be. Maybe there are different alternatives with different trade-offs. If we are to find a good solution, we might need to spend time researching and exploring different options. At the same time, we are always bound by constraints on time and money. Responding to any particular change competes with all the other changes in the queue. This dynamic tends to favor minimal solutions with little immediate risk over optimal solutions for the long-term health of the software.

The physical properties of the code plays a role here, too. Code has a mass of sorts. As such, it exhibits inertia in the face of change. This inertia not a property of “bad” code alone. All code has inertia. As a rule of thumb, the more code, the more inertia. That said, some code is more “dense” than other code, and as such represents more mass per line of code, and exhibits more inertia. But even simple code has inertia and presents an obstacle to change. Change that involves changing code is never free. Again, this favors minimal solutions that require us to change as little as possible of the existing structure.

In general, there will be a residue when we respond to change. We close the gaps, but not completely and not without compromise. In the process, we may incur assumptions that limit our degrees of freedom in responding to future changes. Over time, we accumulate lots and lots of these imperfectly bridged gaps that are woven into the fabric of the code. Compromises, assumptions, and imperfections multiply and ossify. The system becomes rigid and brittle. This is when we start talking about friction and debt. We have painted ourselves into a corner. There are no more minimal solutions.

Various factors influence how effective we are in closing the gaps, but we can’t avoid the fundamental processes at work. At best, a healthy and highly competent team in a healthy environment can minimize the accumulation of residues by favoring solutions that benefit the long-term health of the software, choosing simplicity over sophistication wherever possible, recognizing gaps early, consolidating models and architecture regularly, and investing in the skills and competence of its members. On the flip side, there are many things we can do to speed up the accumulation of problems, both inside and outside the team. We are familiar with many of them: unreasonable deadlines, gamification of arbitrary metrics, high pressure, low trust, no slack, focus on short-term goals, doing the bare minimum, avoiding difficult tasks, neglecting consolidation and structural improvements, and so forth. An enabling problem underlying many of these dysfunctions is a lack of understanding the dynamics of software development, and how the forces and constraints we operate under affect the systems we build.

Our poor understanding of the processes that lead to technical debt is mirrored in the generic and feeble language we use to describe the process of paying it off as well. In fact, it’s a bit of a stretch to call it a language, it’s a single word: refactoring. While refactoring as an activity is fine, by itself it says nothing about the processes that should drive that refactoring. After all, refactoring just means restructuring a body of code. We must do so in a way that restores the system’s ability to handle change and evolution, which means we will need to make significant changes. Chances are, we’ll need help from the business people in the process. We may need to revisit previous decisions and solutions if they don’t form a coherent whole. We may need to look at the assumptions we have made, to what extent they are still valid, and whether or not they hold us back. We may need to come up with a better mental model and language for the problem domain to drive our software going forward. We may have to remove features that don’t fit in. We may need to delete some of our favorite code. We may have to look for constraints we can embrace that will simplify our architecture, make it more consistent, and enable desirable system properties. These are crucial tasks that go way beyond mere code hygiene. Our hope in succeeding lies in the benefit of hindsight. We can apply our learning to make better, more consistent choices retroactively. But it requires an environment that is both capable of learning and understands the necessity of feeding that learning back into the system to improve it.

The nature of software development is such that the entropy of the system must grow over time. We are bound to drift into debt. That’s just the way it is. We can mitigate the effects of that process or we can accelerate it, but we can’t avoid it. This is a basic insight that we developers first need to accept ourselves, and then share with our friends, the business people. We must take collective ownership of this reality if we hope to improve our ability to build software systems that adapt and evolve successfully in a world of constant change.

Loading...

Related

Technical debt isn’t technicalDecember 5, 2015In "Programming"

Conway’s mobSeptember 15, 2020

Death of a CraftsmanApril 5, 2020Liked by 3 people



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK