19 Comments

While this describes tech debt well, it doesn't provide common underlying reasons, and for that there are a couple more concepts that have to be taken into consideration.

1. management measurement and estimation tends to only cover the initial development phase of a piece of work, and tends not to explicitly factor the ongoing and end-of-life costs. As such those costs then tend to get baked into the costs for future work that is coupled to it.

an expedient piece of work with respect to initial delivery, if it were to have an impact on all subsequent work, it reduces the ability to deliver since it's ongoing costs need to be added to all future work.

which leads into the second aspect

2. Coupling - often the expedient (technical debt) decisions allow for the coupling of work. By reducing the coupling, the costs associated with new work tend to be self-contained and not factor in prior maintenance. As such the ability to deliver over time improves even if the initial development might be higher for the decoupled work.

eventmodeling.org goes into more detail in a similar vein, but does include a good diagram to help visualise this : https://eventmodeling.org/cost-comparison.jpg

Expand full comment

That's a great comment James!

Absolutely agree about 1. There is often underestimation of "operating costs" of features. I also wrote an article about this: https://refactoring.fm/p/the-operating-cost-of-new-features

It is also true that high coupling leads to even higher ongoing costs, this should be considered in design.

Expand full comment

The idea of writing code *clean to refactor* really caught my attention. It is only a small shift of perspective compare to writing *clean* code, but it means looking ahead and anticipating instead of being obsessed with the current state of your problem.

I can relate to that in data science especially, where your understanding of a problem changes as you write code and thus have new results to analyze.

But I would be curious to ask what you recommend in practice to write code *clean to refactor*? I feel that building abstractions before it is really needed is a first trap to avoid.

Also, I think that Ward's quote "If we fail to make the program aligned with what we understand to be the proper way to think about our [...] objects" can be interpreted in two different ways:

1 - not having a proper understanding of the situation

2 - having a proper understand of the situation, but failing to actually implement this understanding.

The result is the same: a code which is not a good fit giving the requirements, but the solution to solve this issue could be different in both cases.

Thanks a lot for this great content Luca, I'll keep reading it with pleasure ;)

Expand full comment

Thank you for your great comment Martin!

I believe avoiding premature abstractions is the most important thing. Abstractions are hard to remove when you discover they are wrong — you will be tempted to build on top of them, thus creating more debt.

In my experience, even duplicating code is better than a bad abstraction.

To go back to the two points of your interpretation, I think they are correct, and most problems come from (1). More so, problems arise from the combination of A) not having a proper understanding, and B) not realizing this. So you have these "unknown unknowns" and you end up creating abstractions that do not fit. While instead if you *know* that you don't know, you will write more conservative code, expecting to refactor it later

Expand full comment

"even duplicating code is better than a bad abstraction"

-> nice post on this idea https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction: "the sad truth is that the more complicated and incomprehensible the code, i.e. the deeper the investment in creating it, the more we feel pressure to retain it"

For the last point, we end up with the fact that communication between teams (here business and IT) is the key (once again)

Expand full comment

Thanks, Luca.

I think it's important for us, as techies, to appreciate the company/business stage inorder to fast adopt either alternative.

I'm part of a team where, initially, we'd stuck to "getting the right design from start" until we realized that the product was instead more skewed towards the "rapid evolution" approach.

This article has helped me understand why the "technical debt" is inevitable and why a tech team needs to plan repaying that "debt" very so often least it becomes a time-bomb!

Expand full comment

Thank you Martin! We too fell into the trap that good design leads to no debt. Not true unfortunately!

Expand full comment

Thank you for writing on the subject, I think there is a lack of rigor and discussions of the principle, and so, in practice, it becomes an excuse for whatever an engineer/manager wants to do or not do.

> He doesn't talk of poor code

He directly calls out bloggers mistake tech debt with "dirty" code and Cunningham mentions he's never in favor of writing code any worse than one's best ability(not "perfect", but still high bar). The debt metaphor is more about understanding of domain and internal inconsistency within different parts of the code base (frequently distributed systems).

DRY is one possible tool for keeping internally consistent implementations.

Expand full comment

Thank you Micheal for your thoughtful comment! I believe DRY is a good principle and it definitely helps, but it's not enough.

DRY basically says: whenever you should repeat yourself, just don't, and create a new abstraction. But the fact you were duplicating code doesn't guarantee that the abstraction you are now creating is correct (or that you should create one at all) — that depends on the business side.

Expand full comment

Was there an old Apple way/ saying - get it to work, then do it right, then make it faster?

Expand full comment

It's attributed to Kent Beck as Make it work, Make it right, Make it fast

Expand full comment

Nice article. Well summarized. Above the understanding of technical debt, having ongoing visibility and commitment to clear it are the major problems. Especially in the enterprise space, businesses wouldn't like to prioritize technical debt works when compared with new business features, and this approach continues till they wouldn't move any further with the debt. While writing code that is clean to refactor is important, the shift in the mindset to consider technical debt as an integral part of their journey is also important. By the way, sailor knots is a good metaphor :)

Expand full comment

Thank you Sudheer! One way to address technical debt is to reserve a fixed amount of time to maintenance. We use about 20% of the time for it (refactoring + small bugs). This way you don't have to negotiate it every time.

Of course you can't address all technical debt like this — there are tasks that require a higher effort and should be planned separately.

Expand full comment

"Writing code that is clean to refactor — which is very different from clean. Such code is similar to those sailor knots who should be strong enough to hold for a while, but easy to dismantle when not needed anymore." amazing example

Expand full comment

Thank you Gianni!

When I was young, my father showed me many of such knots. I remember being amazed at the fact that, as a sailor, you had to judge these knots equally for how much they could hold, and for how easy they could be untied.

It's a great memory, and I am happy I could use it in the article 😄

Expand full comment

This situation (Technical debt) is not good. Instead, you can perform situation, take time to build better solutions.

Expand full comment

thanks Luca for this apt article. Could it be possible to mention any real world examples as case studies as well for more benefit to us. thank you.

Expand full comment