2020-11-13

The virtues of not being clever

I encountered a discussion of FizzBuzz (as a a test of basic programming competence rather than in other contexts) out on the internet the other day. In particular the discussion covered the annoying fact that the naive ways of implenting the thing end up giving up a slight degree of inefficiency: it's hard not to have at least one "redundant" condition evaluation.

And I had a brainstorm in passing: a slightly less naive implemnetation that avoids that pitfall. I'm positive I'm not the first person to have through of this but I don't recall seeing it written down anywhere before. So here is the decision code in c++:

#include <cstdint>
#include <string>

std::string FizzBuzz(uintmax_t n)
{
    switch (n % 15)
    {
    case 0:
        return "FizzBuzz";
    case 3:
    case 6:
    case 9:
    case 12:
        return "Fizz";
    case 5:
    case 10:
        return "Buzz";
    default:
        return std::to_string(n);
    }
}

It has only one test and the switch is likely to be efficient. So, it's great right?

Well, no. I think that it suffers relative the naive approach in terms of clarity and conveyance of intent and would generally avoid this kind of cleverness in production code unless there was a big gain from using it.

You see, the switch is horribly opaque. To understand the task by reading the code one has to puzzle over the selection of cases, while naive implentations have the conditions written out explicitly.1 If performance was known to be an issue, I might look at the switch-based implementation, but until then I'd stick with one of the simpler but clearer ones.

Well, look at that!

While looking up links for this post I ran into a blog post that exhibts a switch-based solution albeit in ruby which means the switch has different semantics2


1 Here I am assuming that if ( i % N == 0 ) will be read as "if i is divisible by N", which I suppose is a unusual outside of programming circles. Failing that I suppose the implemntor could write a predicate isDivisibleBy(uintmax_t numerator, unitmax_t denominator).

2 I believe ruby's switch is syntactic sugar for a chain of if statements, while the C++ construct's (often annoying) limitations allow it to be implemented under the hood in a variety of ways that may include jump tables.

No comments:

Post a Comment