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.