Functional Decomposition

Why Do I Have To Have A Function?

function giveBonus(currentYear, price) {
   if ((currentYear % 4 === 0) && price > SUPER_THRESHOLD) {
      return SUPER_BONUS;
   }
   return price > BASIC_THRESHOLD ? NORMAL_BONUS : 0;
}

The above is a small function that gives a bonus. So why should I need more sub-functions?

Personally, I’d probably hope to make the above read like this?

function giveBonus(currentYear, price) {
   if (isLeapYear(currentYear) && 
         priceQualifiesForSuperBonus(price)) {
      return SUPER_BONUS;
   }
   if (qualifiesForNormalBonus(price)) {
      return NORMAL_BONUS;
   }
   return 0;
}

There are some arbitrary names of things in the above, because this is a fictional example. However, notice the extraction of calculations into functions that do a small single-purpose thing.

This, is apparently, contentious.

What Are Our Goals?

We want to write code that:

  • Gets the right answer
  • Is efficient at runtime
  • Is easy to read and maintain
  • Avoids repetition, so has appropriate reuse of business logic

We can collapse these into pairs:

  • Is good enough – output and speed
  • Is maintainable enough – readability and manageable

And when we think about whether it gets the right answer, the maintainability is a good way to ensure it’s going to keep getting the right answer:

  • Can we understand it easily?
  • Can we test it easily?
  • Will modifications really work because of how it’s written

So, fundamentally breaking code down into manageable easy-to-read chunks is at the heart of this.

The above decomposition, even for this one-time logic, is a step towards making all of the above possible at the slight overhead of introducing more function calls into the mix.

Let’s agree there are more function invocations and that they cost machine resources. Let’s not form a decision on how bad that is in practice yet.

Notice, in the above, the isLeapYear function, which was clearly the purpose of the original year % 4 thing makes us wonder if it really handles leap years, for which the logic isn’t quite as simple as was originally expressed. Perhaps this code structure makes us question/find a bug… or maybe it’s just documentation posing as code structure… which isn’t a bad thing.

But You Can’t Just Call Functions All The Time

And now some bad arguments about the above:

  • Where’s the function reuse? – I didn’t do it for reuse – that’s a different concern
  • What about the overhead of calling the function? – how frequently am I doing it, and does it matter?
  • What if it matters? – maybe the runtime will spot this and do some automagic inlining of the function – good compilers/optimisers/runtime profiling optimisers all do this – even JavaScript can
  • Isn’t it spaghetti code? – not by my definition – maybe yours, or maybe not

If you try to avoid calling functions, you end up with long routines with high cyclomatic complexity.

If you try to break functions down into self-describing smaller functions, then you end up with fairly easy to understand, easy to test pieces.

I prefer this approach.

Leave a comment