When Edsger Dijkstra wrote his famous letter “Go-to statement considered
harmful” in 1968 the programming world was split between goto-supporters and
goto-opponents. The first claimed goto simplifies programs and makes them faster,
the others argued it makes programs hard to follow and understand.
Today we live in a world where the goto-opponents clearly won. Almost all modern
programming languages are devoid of goto, and those that have it, use it
sparingly.
Yet today, according to many, we face another monster - if
. Some argue is
as harmful as goto and should be avoided at all cost.
Let’s look at both sides of the argument, and see if there’s any merit in this
fear of if
.
Why if is bad
The argument against if
is strong because many times it can be indeed a code
smell telling you there is something else you should be doing. The crown example
presented by the if-harmfulness camp is some kind of type-switching:
def run_vehicle(vehicle)
# how the hell do I even test this?
if (vehicle.type == :car)
drive(vehicle)
elsif (vehicle.type == :plane)
fly(vehicle)
else
sail(vehicle)
end
end
Any programmer with some experience in Object Oriented programming can clearly
see this is bad - if
is replacing polymorphism. If you see code like this in
one place, you can be sure you’ll find it in twenty other places before you can
even finish reading this sentence.
Why if is any good
When you think about teaching programming, what’s the first thing you want
someone to learn? It’s if
! It’s the most basic construct in nearly every
programming language. Every programmer knows if
, every programmer wrote
thousands upon thousands of if
s. Every programmer knows what if
means and
what was the intention of the coder who wrote it.
There are places where if
is the cleanest form to write something. Can you
find a better way to write a min
function?
def min(a, b)
if a < b
a
else
b
end
end
It’s understandable, self documenting and straightforward. You could also argue it almost reads like English.
And yet I often see some bizarre constructions built only to hide the if
.
Some examples of code I’ve seen are:
def min(a, b)
a < b and a or b
end
def min(a, b)
{true => a, false => b}[a < b]
end
Although you could argue the code is smaller, I will strongly defend the point
that the original implementation with if
is much clearer and simply better.
Yet that is hardly the worst thing - the worst thing is that both those solutions
do not solve the problem we presented above as code smell - conditional
decision. If you try to solve if
code smell I showed in the section above
with code like this, I’m sorry to disappoint you - it’s not solving anything.
But the point I want to make is that if
is often not a problem at all - it’s
the most obvious way to solve a problem. You should not dread if
- it’s
familiar, it’s easy, it’s clear.
statement vs expression
Let’s step back a little and explain the difference between expressions and statements. Expressions have value and can be used in any place a value is expected - as arguments to functions, on the right side of assignment, etc. Statements don’t have values - they are parts that compose programs, they only do things, but they don’t have values.
Most imperative languages have if
statements - you can’t write code like this
in C:
int x = if (foo < 0) {
foo;
} else {
-foo;
}
But such a thing is perfectly valid (besides syntax differences) Ruby code,
because Ruby does not have if
statements, Ruby has if
expressions.
Most languages with if
statements have a reduced version of if
expression -
ternary operator. We can replace the code above with it to make a valid C
program:
int x = foo < 0 ? foo : -foo;
Because Ruby has if
expressions we could do something similar with:
x = if foo < 0 then foo else -foo end
I hope it’s clear how ternary operator is if
expression, only with a
different syntax. That is why I haven’t touched on it before - it’s the same
thing.
if expression vs if statement
Even if Ruby has if
expressions it’s often used as a statement. What do I mean
by that? Think of every place where you use if
, but you discard the value of
the if
itself.
if bar?
x = 100
end
What is the value of x
if bar?
was false?1 It’s not obvious
and error-prone. I really like the approach Haskell takes - if
always needs an
else
clause. This forces you to use if
more as an expression instead of
using it as a statement.
But let’s look at a less extreme example:
x = 0
if bar?
x = 100
end
We don’t have the problem with undefined value, but wound’t it be clearer if we wrote something like this:
x = if bar?
100
else
0
end
We see clearly what is happening - we assign value to x
. And that’s it. It’s
clear and understandable.
For the same reason I don’t like if
without else, I don’t like the suffix
version of if
or unless
- it’s not clear what is the value of the expression
when the predicate was false.
The only exception for this is code that only deals with side-effects and does
not return a value. Think of logging, printing, raising exceptions, etc. For
such code I think using suffix if
or one without else
is perfectly fine.
Conclusion
Excessive use of if
can be often a code smell, especially when the
conditionals concern something that can be considered a “type expression”. But
if
is a powerful construct that is well understood by all programmers and that
should be used in places where it’s required. It’s the most straightforward and
cleanest solution to many problems. It shouldn’t be abandoned too soon.
Let’s devise some simple rules of healthy if
usage:
if
should preferably be used as an expression not statement,if
should be always accompanied by anelse
clause unless evaluating for side effects,- you should avoid assigning inside
if
, - you should avoid type expressions in predicates.
I’m pretty sure that following those simple rules will make your code clearer.
-
The value of
x
will benil
↩︎