by Leon Rosenshein

Poor Man’s Polymorphism

It’s been said that an if statement is polymorphism at its most basic. It’s also been said that if-else is a code smell. Last year I talked about using data and data structures to eliminate if and switch from your code. Coding to interfaces can make your code cleaner and easier to follow. If reducing if is good then eliminating it is great, right?

Not always. A big part of development is building a mental model of what you want to happen. And there’s a limit to how big those models can be. Sure, any decision logic can be turned into a state machine and used to “hide” the details, but sometimes the simplest state machine is just a set of if statements.

The other day I wrote about cleaning up some complicated decision logic. I did go back and clean it up a little more. The code ended up looking like

_, err := os.Stat(path)
if err == nil {
  return true, nil
}

if os.IsNotExist(err) {
  return false, nil
}

return false, err

Which is about as simple as it can get. And it’s obvious. No hidden complexity. No extra layer of abstraction. No deeply nested if turning the code into a right arrow. Sure, we could have written a factory that builds an instance of an interface that does the right thing, but that’s just hiding the if behind one more level of abstraction. And no-one wants that.

So how do you know when to just if it and when to use the interface? It’s a combination of code size and frequency. Replacing 8 lines in one place with a factory/interface construct doesn’t make sense. On the other hand, building your own vtable to map shape types to their different functions (something the compiler can do for you) is just as wrong, and doing it in one giant dispatch function that only uses if is even worse.

So remember, it depends, and let the data guide you.