Nothing Special   »   [go: up one dir, main page]

BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Managing Technical Debt

Managing Technical Debt

This item in japanese

Lire ce contenu en français

Technical Debt is widely regarded as a bad thing; that should be avoided or should be paid back as soon as possible.

Should you? We don't think so. First, we compare Technical Debt with financial debt, explain its similarities with Strategic Design and which stakeholders it surprisingly has. Then we list the various possibilities to identify the Technical Debt in your code, which you perhaps have to take care of.

Finally, we describe different ways that a project could pay back Technical Debt and which considerations must be made in order to decide if you should better repay, convert debt or just pay the interest.

What is Technical Debt

Developers have the choice to implement new features in two different ways: one is to do it quickly and messily, which will make future changes very hard. The other is a clean and smart solution, which takes longer to implement, but makes changes easier in the future (see also Martin Fowler). But why should the sponsors of a project accept higher costs for a clean implementation of a feature if the same feature, implemented with a messy solution, delivers the same functionality and costs less? Why should they spend money on automated test coverage? Tests are not features and therefore don’t deliver business value!

Although messy code or code without tests works perfectly for customers if it delivers the desired business value, this will lead to an uncontrollable code base, extremely specialised developers and eventually to an inflexible software product. A sufficient amount of messy code may bring a whole engineering department to a stand-still.

The metaphor ‘Technical Debt’ - Similarities and differences to ‘financial debt’

Ward Cunningham used the metaphor ‘Technical Debt’ for the first time in 1992 to communicate this problem with non-technical stakeholders. Code with low quality and no automatic test coverage can be compared with financial debt. This code is like a financial burden, which imposes on all stakeholders - not only on the developers - a debt incurring interest for the future. The principal amount is the cost of refactoring the codebase to a clean design, which allows easy future changes. The interest is the extra costs, which have to be paid in the future if the team has to work with a messy codebase instead of a good one.

Unlike financial debt, you don’t have to pay back Technical Debt. Sometimes paying it back would even be pointless: some pieces of code are rarely or never read or changed. Therefore Technical Debt also needs to take into account a probability of occurrence - how probable and how often will the messy code be touched in the future? Another difference to financial debt is that the originator of Technical Debt won’t necessarily pay back the debt himself, but rather the developers who maintain the codebase later.

Like financial debt, Technical Debt is not necessarily a bad thing. Going into debt to buy a house is responsible if you know how to pay it back. Buying lots of luxury items on your credit card knowing very well you’re not able to cover the bill usually ends up in a disaster. Concerning software Technical Debt might give an advantage of an early release and profit the organization more than it costs to pay back the Technical Debt. In finance, debt is a good thing, if the principal amount plus the interest is lower than the yield of the investment. The same is true for software. If you sacrifice internal quality for being the first on the market, it only pays off if the yield you make with this decision is higher than being later to the market with better internal quality. There is, however, a risk, because it is hard to estimate these benefits upfront since there is a degree of uncertainty.

Technical Debt and Strategic Design

The concept of strategic design by Eric Evans gives us an idea how to deal with Technical Debt. Strategic Design says that a system can’t have the same high level of quality throughout the system. Therefore a team can choose to either leave to chance which parts of the system have a good or a bad quality or to actively control the quality. Messy code, which is rarely read or touched and doesn’t implement important requirements, does not have to be absolutely perfect and therefore we don’t need to spend a lot of effort on refactoring it into great code. So the question is which parts of the code should have high quality? It’s possible that a piece of the implementation has a bad design without having a bad quality - if no good design is required for that piece of the implementation. This argument is of course questionable: while each particular piece of code doesn’t need to have good quality, in summary you might be creating an unmaintainable system.

Understanding Strategic Design and Technical Debt leads to better communication within a project and therefore to better decisions from the stakeholders of a software project. Developers might realize that not every requirement has to be implemented elegantly, if this requires too much effort. Also the customer understands that quick & dirty solutions lead to debt the project eventually has to pay back. Technical Debt consists of hidden quality problems, which like an iceberg might lead to a failure of the project once they surface e.g. through too many bugs it is most likely too late to fix the quality problems cost-effective.

Stakeholders of Technical Debt

Technical Debt concerns many stakeholders of a software project:

  • Customers are annoyed by bugs or missing features due to low productivity.
  • This leads to additional costs for the helpdesk, which annoys the people there, too.
  • Increased development time and quality issues are also a problem for marketing.
  • Bugs lead to frequent patches, which annoys the operations team.
  • Many annoyed parties definitely don’t make the management happy - especially if there is bad publicity due to bugs, delays or security problems.
  • Last but not least also the developers are suffering. No one wants to deliver bad work. Furthermore, no one wants to take over the bad work of others.

Technical Debt is unavoidable

But why don’t many projects do it right the first time and avoid Technical Debt? Assuming that developers aren’t lazy and are experienced with the relevant technology and patterns, than Technical Debt is almost always enforced by time pressure. The developers hope to gain a higher short-term productivity with a quick & dirty implementation and lower costs for the current release - well aware that they cause lower productivity and higher costs in future releases. The team, however, can never be really sure that it actually achieves a higher productivity or lower costs within the current release. Possibly, the shortcuts boomerang earlier than expected and the team has to pay the interest earlier than expected.

But we as developers usually don’t really want shortcuts or compromises in quality. Sometimes the external conditions are just that things within a release have to be done quickly, because otherwise there won’t be a next release. Or the team assumes that the code won’t be read or changed often. The previously discussed “Strategic Design” definitely allows compromises in quality. In that case good code might rather be “gold-plating”, an overkill.

Thus we see, that the Technical Debt metaphor helps all stakeholders to communicate the problem of poor quality that might otherwise go unnoticed until it is too late. The subsequent damage due to bad code quality and the increasing degeneration of the quality are very well represented by the metaphor of interest. But the metaphor also falls a bit short: not all Technical Debt must always be repaid. The team also doesn’t know exactly how much the debt is and when it needs to pay it back. The person who obligates the debt is not necessarily the one to repay it, but usually someone else. Fancy that in real life!

Identifying Technical Debt

A major problem of Technical Debt is that it’s not obvious. Anyone can see the amount of debt on an account statement. But how can a team actually recognize Technical Debt? What are the indicators?

  • General “smells” that manifest themselves in statements from the team members: “the only one who can ever change this code is Carl”, “let’s just copy & paste this code”, “if I touch that code everything will break” or too many TODOs or FIXMEs in the code (nicely described by Nico Zazworka).
  • Scrum-teams have a velocity. The software system has probably too much Technical Debt if the velocity decreases even though the team wasn’t changed and the external circumstances did not change,
  • Software ages. One indicator for Technical Debt is when a system uses very old libraries, that are no longer being maintained or in a new version are much more productive (e.g. EJB 2 vs. EJB 3). In the worst case, the libraries are already so old, that the developers of the libraries no longer even support them.
  • Automatically measuring Technical Debt is partially possible. Correctly configured, tools like Sonar, SonarJ or Structure101 find important violations of best coding practices. As this blog post shows, this is not the ultimate solution, but a very good one. With Sonar you ensure that developers follow important code metrics like appropriate class and method size or low cyclomatic complexity. Tools like Structure101 find structural problems. These include cyclic dependencies. If two elements depend on each other, any change in one of the elements potentially affects the other element. These two elements can only be changed together - although they actually should be separated and should be developed independently. Furthermore Structure101 finds complex code: it is all about the dependencies among the elements. A system is really hard to understand and therefore to maintain if its classes or packages have too many dependencies to other classes or packages. If a developer wants to make a change he has to understand many classes and packages and has to consider many dependencies. These problems point to key challenges in the architecture.
  • Code coverage tools can detect how much code is really covered by automatical tests. This metric should be used with care, since there are no general guidelines. Generally a coverage of more than 90% is a good indicator that there are enough test cases available. Conversely, a coverage below 75% may indicate a serious problem.
  • But there are also a lot of cases in which systems have Technical Debt, but it cannot be measured directly in the code, e.g. clumsy solutions, wrong choice of technology, a well-executed, but wrong design for a solution or simply evil hacks that cannot be detected easily by tools. In these cases Technical Debt must be measured differently: the bugs per release rise rapidly, the velocity decreases permanently or the team is under extreme stress at the end of a release.
  • A very bad indicator are frequent problems in production. This means that the problems in the system are so extensive that reliable operation is no longer possible.

All of these indicators can be measured, but not always directly in the code. The sooner these problems are solved, the cheaper. If developers identify Technical Debt, communicate it, and nothing happens, sooner or later business people will feel these problems too. This statement clearly shows: Technical Debt is not a gimmick of developers who want beautiful code. It is a tangible cost factor and may be a risk for a project. Therefore Technical Debt must be made visible and manageable. As in real life, debt is not necessarily a bad thing, but it must be used consciously and properly. This means for software projects that paying back Technical Debt is a pure business decision. It must not simply a developer's decision.

How to manage Technical Debt?

We have seen that Technical Debt cannot be ignored. Even the non-technical management on the client side must have an interest in managing Technical Debt as smartly as possible to achieve the best balance between short-, mid- and long-term success. What can a team do to avoid wasting its time with unimportant beautification but still make meaningful business decisions for improving their code quality?

Coarse-grained statements such as “do nothing” or “if the code is unmaintainable anymore, develop anew” do not help.

We consider two promising approaches in this article, which have already found useful in several projects:

  • Technical Backlog
  • Include cost for Technical Debt in requirements estimation
  • Prior to that, we want to discuss two other critical processes that probably make sense in certain project contexts:
  • Buffer-tasks for refactoring
  • Cleanup-releases

One question, which must also always be addressed in the discussion of Technical Debt: must Technical Debt ever be repaid? Frank Buschmann describes 3 strategies, which we address later on:

  • debt repayment
  • debt conversion
  • just pay the interest

Buffer-task

The team creates one buffer-task per release with e.g. 10% of the available time. Team members can record the time on that task for not yet scheduled refactorings. So it is used for yet unknown problems that might appear in the future. Such a buffer is very easy to schedule and use. However, it also poses the risk that time is wasted on unimportant work. A buffer doesn’t force anyone to consider whether the time is spent for useful refactorings or not. The developers just record their time on the buffer task. Most likely the time of the buffer may not be optimally utilized - and especially deciding which refactorings will be done, although that should really be a business decision. Using buffer-tasks unfortunately means that what should be done is not really defined.

Cleanup-releases

Some teams do a purely technical release to improve the codebase from time to time. This approach is only useful if a list with the really necessary refactorings already exists. Otherwise the team risks wasting time on unimportant refactorings. This approach must also be supported by the business side because it might delay new features. Of course this requires that business people understand Technical Debt. You should think about a pure technical release to clean up the codebase and to rework the architecture if some major effort is needed. For example the same parts of the code might always cause problems during development or operations, the current architecture might no longer fit the current requirements. Such problems cannot be solved within small refactorings. A cleanup release allows extensive changes.

It makes no sense to do a cleanup-release after a very hectic and time-critical release that has created a lot of Technical Debt. There is only little experience with the new code base, so no one can say which part of the code needs to be improved. The danger in changing code which does not really need improvement.

Technical Backlog

The technical backlog is an established best practice to define purely technical work packages. Tasks for this purpose are created in a task tracker or requirements management tool. Each task has a brief description of the technical change to be made, why this technical change is important for the project and in which part of the code the technical change has to be performed. As with any other task, we need an estimate of how long it takes to develop a good enough solution. In addition we need to estimate the interest which is inherent in this code. A precise estimation is difficult, but often a rough estimate such as 'small', 'medium' or 'high' is sufficient to guide a decision. Ultimately we also need a probability: how likely will this code be read or changed in the not distant future?

This approach has several advantages:

  • Technical Debt is made visible and clear for everybody. A unanimous decision can be made if and when a refactoring task should be done based on the estimated effort and the impact on future code changes.
  • The cost per task is easily trackable
  • There is no mixture between technical tasks and feature tasks.
  • However, this approach also has some disadvantages:
  • The customer must decide about the backlog and prioritize tasks. The customer can do this only to some degree concerning technical issues since he cannot determine the value of a technical task without detailed knowledge of the software.
  • In addition, the customer may not really understand the business benefit of a purely technical task. In many cases, there will be some mistrust why a technical task without a feature relevant to the customer is really needed.

In our opinion only in special situations is the customer able to decide that e.g. with software updates. It is obvious to most customers that outdated software causes problems. Whether an update should be done has to be decided case by case based on cost, risk and necessity of the upgrade. Updates of components such as the Java Runtime Environment, or an O/R mapper do usually not cause a lot of effort and have a rather small risk if they are done regularly. Very expensive changes e.g. the replacement of a web framework or a change from a relational database to a NoSQL solution must always have a business reason, e.g. the performance or the user experience of an application must be improved. For this purpose the technical backlog is also well suited. But for such tasks you may also initiate a separate project, depending on how much effort is expected.

The technical backlog is not so great if a new requirement should be implemented in a codebase and that codebase is not well designed for this new requirement. In such a case a refactoring might be needed before implementing the requirement. To do this, you have to add the extra effort for the refactoring to the estimation of the requirement.

Requirements estimation with effort for Technical Debt

Strategic Design has taught us that not every part of the codebase must be very good but only those parts whose changeability brings business value or need to be changed frequently for other reasons. So concerning Technical Debt code quality of rare or never changed locations can be ignored. Also important: Code should never be refactored without a good reason. So if a new requirement provides concrete business value then we should never implement this requirement based on badly written code but refactor beforehand. That way the really important requirements can be implemented to a “good enough” standard. When the team has been assigned to implement such an important feature in an upcoming release, the refactoring should be budgeted and then implemented. Of course there are exceptions, which we will discuss in the section “To pay or not pay”.

This procedure ensures that no unnecessary beautification work is done and that refactorings are always directly associated with a requirement. This allows the customer to discuss and prioritize the refactoring with the team. The customer can also decide whether or not a requirement should be implemented based on debt and interest. There might be other factors to take into account such as time-to-market. The customer might also already know about future changes to the code which are based on these requirements. He might therefore decide to refactor to a clean code base in order to save costs in the future.

Problems arise if the team runs into a problem with the code base during the implementation. Then the team must decide what to do: Maybe meeting the delivery date with all promised functionality is very important. Then the resulting Technical Debt and the interest payments have to be accepted. Or all development based on badly written code is considered to be waste and there is no real deadline pressure. Then the team should schedule the refactoring which causes a later release date. This can only be decided depending on the context and in particular the needs of customers.

To pay or not to pay

We have seen that only important parts of the code must be really clean. So code should only be refactored if necessary. However, it is hard to decide what solution might be best for a specific scenario. Frank Buschmann describes 3 strategies:

  • Debt repayment: refactor or replace the code, framework or platform that is considered a Technical Debt.
  • Debt conversion: Replace the current solution with a ‘good, but not perfect’ solution. The new solution has a lower interest rate. That might be a good option if a perfect solution is prohibitively expensive to build.
  • Just pay the interest: live with the code, because refactoring is more expensive than work with the not-quite-right code

Developers and customers have to decide that on the basis of cost, risk and urgency. In summary, such a substantial refactoring should always be a business decision. Another important factor for paying interest: the "end-of-life" of the software - is a refactoring valuable at all, if the system will be redeveloped or retired soon anyway?

Conclusion

Technical Debt can be seen as a shortcut, which saves teams’ time, effort and/or money today, but might lead to increased costs in the future. Technical Debt cannot really be avoided in software projects. But it is not necessarily a bad thing, if handled properly.

Doing so is difficult: The effect bad code quality has for future requirements is difficult or even impossible to predict. However, completely ignoring Technical Debt can have disastrous impacts on the software. If the team and the customer handle the risk of Technical Debt properly, it should remain manageable. We have presented different ways to respond to Technical Debt. Each of these options should only be used in a specific context. It is important to accept:

  • that there is always Technical Debt
  • that Technical Debt is not always bad
  • and that Technical Debt does not have to be (fully) repaid in every case.

Any repayment should be a business decision. Even if the repayment is so small that a consultation with the customer does not make sense, the developer must always ask himself whether an improvement of the code really justifies the invested time and effort.

About the Authors

Sven Johann is working as a Software Developer for Trifork Amsterdam. Sven is an avid user of XP practices like pair programming, TDD and small releases. Currently he’s developing online assessment software for schools in Switzerland and The Netherlands based on Trifork’s QTI engine.

 

 

Eberhard Wolff is working as Architecture and Technology Manager for adesso AG in Germany. His focus is on Java, Cloud and Software Architecture. He is a regular contributor at international conferences and author of several books and articles.

Rate this Article

Adoption
Style

Related Content

BT