Calculating the “interest” on technical debt.

Categories: Software Development

“Technical debt” is one of those terms that experienced developers throw around when people ask them to do something they don’t really want to do. To be fair, many times we don’t want to do something because experience has taught us that short-cuts taken today will cost us in the future. But how much will it cost, exactly?

Last week, a seemingly small code change led to a big epiphany for me. It is actually possible to calculate in advance the future cost of technical debt!

I’ll give a concrete example. In our integration code, we use dynamic module loading to route inbound requests to different third-party systems:

provider = 'rms'

. . .

provider_module = import_module('providers.risk.%s' % provider)

return provider_module.get_risk(payload)

This pattern satisfies at least all of the consonants of SOLID, and has worked very well for us throughout the code base. To add a new third-party integration, we create a properly-named module and we can start calling it immediately without touching the rest of the code base. So the overhead cost of adding a new third-party module is essentially 0 lines-of-code (oc = 0).

But now we want to enhance this interface. We want to do some basic validation of the payloads that are passed to our integration modules. We handed this task to a junior developer, who wrote the following code:

from providers.risk import rms
from providers.risk import air

. . .

provider = 'rms'

. . .

provider = import_module('providers.risk.%s' % provider)
if provider == rms:
    missing_params = payload.missing_from_set('risk-rms-in')
elif provider == air:
    missing_params = payload.missing_from_set('risk-air-in')

if missing_params:
    raise ValidationError(f'get_risk: missing required payload values: [missing_parms]')

return provider.get_risk(payload)

So, while this solution will work, it has fundamentally changed how this module functions. Now it is necessary to explicitly import every module that could be called. It is also necessary to add an elif statement for each new module. Barring other considerations (like importing unneeded modules, etc.) the overhead cost of this solution is now 3 lines-of-code (oc = 3) for each new third-party integration.

But is this cost really needed? Is it possible to refactor this code to return oc to 0? With appropriate naming conventions, the answer is a resounding yes:

provider = 'rms'

. . .

missing_params = payload.missing_from_set(f'risk-{provider}-in')
if missing_params:
    raise ValidationError(f'get_risk: missing required payload values: [missing_parms]')

provider_module = import_module('providers.risk.%s' % provider)

return provider_module.get_risk(payload)

Now new third-party modules can be added without touching this dispatch module. The overhead cost is back to 0 (oc = 0). While this is just a small example, consistently making good choices in the short term leads to a low-oc code base in the long term.

Do you have examples like this from your own experience? Join the discussion on LinkedIn.

«