There is this concept of technical debt, which illuminates why code always becomes ugly over time. Often a feature is not implemented as cleanly as possible, but as fast as possible. Documentation, unit tests, edge cases or comments are deferred and then forgotten. This debt accumulates and compound interest comes in and at some point it's cheaper to rewrite the thing from scratch. (but don't rewrite from scratch anyway!)
Financial crisis aside, what has to be done with debt? It has to be managed. Hastily paying off is not always the best way. Sometimes other things are more important. To manage debt, you need to know how big the debt is. Since we can't really measure productivity, this looks impossible. This article argues that technical debt can be quantified.
Consider the mantra of Test Driven Development:
Red ⇒ Green ⇒ Refactor
First a unit test for a new feature is written and fails because the feature isn't there yet. Then the feature is implemented and the test becomes green. Finally the code is refactored and "code smells" are removed, while all tests are kept green.
The question is why should the third step be done? The application already works and the user doesn't benefit from refactoring. It is possible to skip the third step, but this means that technical debt is introduced. So refactoring could be defined as paying off technical debt, but note that bugs do not account as technical debt now.
Paying off technical debt.
Since programming styles are quite subjective there is no general fixed point, where code can't be improved anymore without changing behaviour. Nevertheless it's reasonable to assume a small region of subjective differences around some fixed point. Given some coding guidelines this region is infinitesimal. So I conclude this fixed point exists.
When refactoring reaches this fixed point the code is debt-free. The amount of debt is thus exactly the refactoring work needed to reach the fixed point. It also means, if refactoring could be done algorithmically, technical debt could be paid off for free. See for example the KABA tool, which can automatically refactor class hierarchies.
Empirically we can say that technical debt is real and we haven't found a way to pay it off for free so far. The need for unittests for example would be reduced, if (algorithmical) refactoring guarantees unchanged behaviour. On the other hand it seems implausible that this problem is not computable, more likely it's not efficiently computable and nobody has ever really tried. For now there is no such solution available and refactoring is done more or less manually in three steps.
- identify "code smells" (exit if none are found)
- refactor them
- goto 1.
Because of the loop in step three, not all code smells can be found in the unrefactored code. So a first improvement would be to break up the loop. For this we need to identify all smells in the first step. Is it possible to write a Lint (software) which identifies all problems in code that keep the code outside the fixed point region? This tool would output a todo list and a project manager should be able to estimate the time needed to fix all items on the list. Thus technical debt would be quantified.
I don't agree that technical debt can't be quantified. Code smells are detectable (I believe) and maybe the loop problem can be ignored in practice. The biggest hurdle is probably the massive engineering effort needed to implement this super lint tool.
Update: Read also Embrace Technical Debt by Eric Ries