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 ifs. 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 an else 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.


  1. The value of x will be nil ↩︎