This post is written mostly as a response to that discussion thread. I was advocating to never use if conditional statement and many people were arguing.

TL;DR: The text below is very biased and opionated. That does not mean it’s plain wrong. I consider if statements a code smell. Period.

I am not a blinkered moron, though, and I understand there are circumstances under which if might be of perfect use. The rule having a dozen of exceptions is hard to memorize, that’s why I state the rule as “don’t use if statements.” “Unless you pretty fine understand why do you use if,” adds my internal rubber duck whom I have proofread the draft of this writing to.


Stone Age

In the stone age there were no programming languages and to kindly ask the machine to perform any operation, humans were using codes that were understood by the machine as is. One of the first books I was able to get to reveal the the mystical veil of development was one of these by Peter Norton. Sorry, Peter, I do not remember which one. It started with a chapter explaining the source of Norton Disk Editor utility, written completely in machine codes. Line by line. It looked like that (don’t try to run it, I just randomly generated a garbage for the sake of an example):

61 01 67 30  30 8D 65 A4
E1 4C 9F 3F  96 B1 EB 85
F6 54 92 B8  51 E1 E5 7D
05 93 EC 2D  BF 10 34 FB
98 AD E0 96  B7 52 21 09
50 FA 45 59  05 DA 17 CB
6B 95 0C 4F  E7 24 60 1F
DE 5D BA F9  72 B7 F8 09
03 10 13 1A  8D 75 79 BF
5E 8D 05 B3  F0 2C F6 D8

I spent a week trying to understand the code and finally realized, I was worse than any machine in understanding codes. (FWIW, I still am.)

I believe I was not the only person frustrated by the necessity to talk to machines using their language, and Assembly came to the scene.

That was indeed a huge step towards preserving sanity of human minds. Now the program listing became …uuuhm… readable:

mov al, 42
mov ah, .answer
cmp al, ah
jne .answer_err

Move 42 to the register named al, move the value of .answer variable into the register ah, compare values and jump to .answer_err instruction if values are not equal. If this is not a poetry, I don’t know what is.

The reason behind this structure of the code is that the CPU cannot permorm some sophisticated operations, like “make a cappuccino.” It can add integer numbers, and it can transfer a control. That’s it.

Note for those bored and falling asleep: I am not going to describe the whole evolution process of programming languages. Actually, I just wanted to show this piece of assembly because I am going to refer to it later.


We Are Human Beings

Unlike computers, we don’t need to decompose compound tasks into the sequence of additions and conditional jumps. We just know whether we want to eat an apple right now or not. There is no if implied there. We don’t think like “if I want an apple, I’ll eat it, otherwise I won’t.”

This is important.

I don’t know how exactly it’s done inside my head (I have heard, even neuroscientists are somehow in doubt,) but from my point of view it looks like “Oh, an apple, I’ll take it.” Or I just ignore it. I don’t think about what I’d do if I wanted it, I just damn don’t give a shit. Pardon my appleish.

Every single morning, save for weekends, I am leaving my house, heading towards the office. I use a metro to reach the office building. The route has one transfer. And, for the sake of this post, I decided to write a code, that could deliver me to the office.

I am going to use ruby, but the syntax should be considered a pseudocode.


Making Decisions

The first naïve attempt would be to use if to check if today is a workday in the first place. Because, you know, I am not writing my PhD anymore and I don’t work on Saturdays. Not in the office, at least.

That said, the early adopted intent would be to start with something like:

if (1..5).cover? Date.today.wday
  go_to_the_office
end

Well, so far so good. The code is clean and easy to read. But wait, on Tuesdays and Thursdays I go to gym:

today = Date.today
if (1..5).cover? today.wday
  if today.tuesday? || today.thursday?
    go_to_the_gym
  end
  go_to_the_office
end

Oh, jeez, I have forgotten to mention that on Wednesdays I am working from home:

1  today = Date.today
2  if (1..5).cover? today.wday
3    if today.tuesday? || today.thursday?
4      go_to_the_gym
5    end
6    if !today.wednesday?
7      go_to_the_office
8    end
9  end

At this point the code is already very hard to maintain. Also, there are 9 LOCs and we all expect it should be easy to tell what the code is doing, but unfortunately that’s not the case. The real code there is actually contained in lines 4 and 7, all the other stuff is about checking conditions.

Sidenote: yes, I pretty fine understand this example is highly exaggerated, there are postfix conditionals in ruby etc. But trust me: what had been started as a single nifty if sooner or later will become this kind of spaghetti monster. The road to hell is paved with good intentions.

Stop Making Unnecessary Decisions

The pill would be to use so-called “Fail-fast” ideology.

OOP Approach

Once I decided to provide a ruby code snippets, and ruby is somehow an object-oriented language, let’s start with just OOP to implement the desired functionality. Prepare to see an unexpectedly huge boilerplate, but it’ll pay back very soon (if one is writing a one-time-run shell script, I’d suggest to stop reading this and go with an if.)

module Schedule
  class Day
    def trip!
      go_to_the_gym
      go_to_the_office
      go_home
    end
    def go_to_the_gym
    end
    def go_to_the_office
      Metro.office!
    end
    def go_home
      Metro.home!
    end
  end
end
%w[mon fri].each do |wd|
  Schedule.const_set("#{wd.capitalize}Schedule", Class.new(Day) do
  end)
end
%w[tue thu].each do |wd|
  Schedule.const_set("#{wd.capitalize}Schedule", Class.new(Day) do
    def go_to_the_gym
      Metro.gym!
    end
  end)
end
class Schedule.WedSchedule < Day
  def go_to_the_office
  end
end

Now we can instantiate the respective class, basing on current week day and call it’s instance trip! method. For both Saturday and Sunday this instantiation would throw an exception, which is just fine (assuming we handle exceptions on the top level and don’t leak them as is to the user.)

FSM Approach

Finite-state automata could be used instead of OOP. The code would be looking pretty much as in the previous (OOP) approach, save for instead of different classes we’d use different transitions from the state home depending on the day of the week. One might argue that there will be ifs, and I’d say yes, but a) they might be completely avoided if desired and b) they are hidden for the user of this FSM behind the proper abstraction. Also, SRP won’t be violated.

Since Ruby does not have an FSM implementation in the core lib, I am to omit the code example here. It’s more or less trivial.

Pattern matching

Ruby does not support pattern matching out of the box, therefore we’d use Elixir to demonstrate the control flow.

defmodule Schedule do
  def trip!(day) do
    go_to_the_gym(day)
    go_to_the_office(day)
    go_home(day)
  end
  def go_to_the_gym(day) when day in ~w|tue thu| do
    Metro.gym!
  end
  def go_to_the_gym(day) when day in ~w|mon wed fri| do
  end
  def go_to_the_office("wed"), do: :ok
  def go_to_the_office(_)
    Metro.office!
  end
  def go_home(_)
    Metro.home!
  end
end

As in the OOP example, the code is readable, maintainable, and—most importantly—extendable.

This Is Not True

Well, it indeed is. Whether in doubt, try to add the weekend activity to any of the aforementioned approaches, then try to do the same with the very first nested ifs structure.

Conclusion

Any time I found myself blindly typing if, I pause for a while and talk to my internal rubber duck (her name is Jess, btw.) “Jess, is there any way to avoid if clause here?” I ask inevitably. And you know what?—In most cases she responds with “yes” and we invent a robust well-designed solution for a problem together.

Sometimes she responds “no,” it’s a perfect use-case for an if, like yesterday when I was to implement a function producing a string representation of current time for an American audience. I ended up with

if hours < 12 then 'AM' else 'PM'

I understand why I used if there (thanks, Jessica,) and it was a last resort. It was a wisely made decision. There is no room for any abstraction. The calendar and the string time representation are not going to change until I am retired.

In all other cases I ended up with a better abstraction, that saved me tons of hours of refactoring, just because I had asked myself (and Jess) whether we could do better than just if. And we usually did.

That is why I insist on memorizing the rule if statements are a code smell, period.” Even despite it sounds too cruel and arrogant. Vague and blurry rules always lose. Solid rules do win. And they surely do have exceptions.