# How to Get Rid of Annoying IFs Forever

TL;DR Don't use IFs in your code.

Nobody uses [GOTO instruction](https://en.wikipedia.org/wiki/Goto) anymore and few programming languages still support it.

We have matured, and confirmed [spaghetti code](https://en.wikipedia.org/wiki/Spaghetti_code) is unmaintainable and error-prone.
[Structured Programming](https://en.wikipedia.org/wiki/Structured_programming) solved that problem years ago.

We got rid of the sentence thanks to [Edsger Dijkstra's](https://en.wikipedia.org/wiki/Edsger_W._Dijkstra) incredible [paper](https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf): Go To Statement Considered [Harmful](https://en.wikipedia.org/wiki/Goto#Criticism).

The next evolution step will be removing most [IF statements](https://en.wikipedia.org/wiki/Conditional_(computer_programming). 

**IFs** / **Cases** and **Switches** are *GOTOs* disguised as structured flow. 

Our tool will be [Object-Oriented](https://maximilianocontieri.com/explain-in-5-levels-of-difficulty-object-oriented-programming) Programming principles.

![There are two roads every time we encounter an IF statement](https://cdn.hashnode.com/res/hashnode/image/upload/v1604271082172/KS_g__hhm.jpeg)

Photo <a href="https://pixabay.com/es/users/przemko-1606435/">Przemysław Bromberek</a> en <a href="https://pixabay.com/">Pixabay</a>
 
 # The Problem

Most **IF** sentences are coupled to *accidental decisions*. This coupling generates ripple effect and make code harder to maintain.

%[https://maximilianocontieri.com/coupling-the-one-and-only-software-design-problem]

IFs are considered as [Harmful](https://sddconf.com/brands/sdd/library/If_considered_Harmful_-_Jules_May_-_SDD2017.pdf) as **GOTOs**.

There's an excellent talk below (If considered harmful: How to eradicate 95% of all your bugs in one simple step - Jules May])

%[https://www.youtube.com/watch?v=z43bmaMwagI]

*IF sentences* violate [open/closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle). Our designs will be less extensible and closed to extension.

What is more, **IFs** are open doors to even worse problems, like **switches**, **cases**, **defaults**, **return**, **continue** and **breaks**.

They make our algorithms darker and force us to build *accidentally complex* solutions. 

%[https://maximilianocontieri.com/no-silver-bullet]

People out of software development cannot explain why we use this branching sentence. This is a code smell.

%[https://maximilianocontieri.com/code-smell-36-switchcaseelseifelseif-statements]

# Solutions

Before we move on and remove IF sentences we should decide if it's an *essential* one or an *accidental* If.

To check this out we will look for answers in real world through bijection.

%[https://maximilianocontieri.com/the-one-and-only-software-design-principle]

## Essential Ifs

Let's see an *essential* **IF** statement

[//]: # (https://gist.github.com/mcsee/ab050467c32205a1b8623352f6bb8dd8)

```
class Moviegoer {
  constructor(age){
    this.age = age;
  }
  watchXRatedMovie() {
    if (this.age < 18)
      throw new Error("You are not allowed to watch this movie");
    else
      this.watchMovie();
  }
  watchMovie(){
    //..
  }
}

let jane = new Moviegoer(12);

jane.watchXRatedMovie();
//Throws exception since Jane is too young to watch the movie
``` 

We should decide whether to remove this **if sentence** or not.

We must understand whether it represents a business rule (*essential*) or an implementation artifact (*accidental*).

In the case above we will honor our [bijection](https://maximilianocontieri.com/the-one-and-only-software-design-principle). So we will NOT replace the if.

> People In real world describe age constraints in natural language using **IFs**

## Accidental Ifs

Let us dive now into *bad* **IFs**.

[//]: # (https://gist.github.com/mcsee/3c0d6d285a537041900a92f71c9c73c8)

```
class Movie {

  constructor(rate){
    this.rate = rate;
  }
}

class Moviegoer {
  constructor(age){
    this.age = age;
  }
  watchMovie(movie) {
    if ((this.age < 18) && (movie.rate == 'Adults Only'))
      throw new Error("You are not allowed to watch this movie");
    
    //watch movie
  }
}

let jane = new Moviegoer(12);
let theExorcist = new Movie('Adults Only');

jane.watchMovie(theExorcist);
//Jane cannot watch the exorcist since she is 12
``` 

The movie rating **IF** is not related to a **real world If** but to accidental (and coupled) implementation.

Our design decision was to model *ratings* with *strings*.

This is a classic *neither open to extension, nor closed to modification* solution.

Let's see what happens with new requirements.

[//]: # (https://gist.github.com/mcsee/c237bf02c4ec561939b7ec5973fc913a)

```
class Movie {

  constructor(rate){
    this.rate = rate;
  }
}

class Moviegoer {
  constructor(age){
    this.age = age;
  }
  watchMovie(movie) {
    //!!!!!!!!!!!!!!!!! IFS ARE POLLUTING HERE !!!!!!!!!!!!!!!!!!!!!!!!!!
    if ((this.age < 18) && (movie.rate == 'Adults Only'))
      throw new Error("You are not allowed to watch this movie");
    else if ((this.age < 13) && (movie.rate == 'PG 13'))
      throw new Error("You are not allowed to watch this movie");
    //!!!!!!!!!!!!!!!! IFS ARE POLLUTING HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!
    
    //watch movie
  }
}

let theExorcist = new Movie('Adults Only');
let gremlins = new Movie('PG 13');

let jane = new Moviegoer(12);

jane.watchMovie(theExorcist);
//Jane cannot watch the exorcist since she is 12
jane.watchMovie(gremlins);
//Jane cannot watch gremlins since she is 12

let joe = new Moviegoer(16);

joe.watchMovie(theExorcist);
//Joe cannot watch the exorcist since he is 16
joe.watchMovie(gremlins);
//Joe CAN watch gremlins since he is 16
``` 

We can detect some *Code Smells*:

1. Code is polluted with IFs.
2. A default statement is missing.
3. New ratings will bring new IFs.
4. The strings representing ratings are not first class objects. A typo will introduce hard to find errors.
5. We are forced to add getters on *Movies* to take decisions.

# The Recipe

Let's fix this mess with these steps:

> 1. Create a Polymorphic Hierarchy for every IF condition (if it doesn't already exist).
> 2. Move every *IF Body* to the former abstraction .
> 3. Replace IF Call by polymorphic method call.

On our example:

[//]: # (https://gist.github.com/mcsee/747c2a4a6bb3531966b7e98ad92924e3)

```
//1. Create a Polymorphic Hierarchy for every IF condition (if it doesn't already exist)
class MovieRate {
  //If language permits this should be declared abstract
}

class PG13MovieRate extends MovieRate {
  //2. Move every *IF Body* to the former abstraction 
  warnIfNotAllowed(age) {
    if (age < 13)
      throw new Error("You are not allowed to watch this movie");    
  }
}

class AdultsOnlyMovieRate extends MovieRate {
  //2. Move every *IF Body* to the former abstraction 
  warnIfNotAllowed(age) {
    if (age < 18)
      throw new Error("You are not allowed to watch this movie");    
  }
}

class Movie {
  constructor(rate) {
    this.rate = rate;
  }
}

class Moviegoer {
  constructor(age) {
    this.age = age;
  }
  watchMovie(movie) {
    //3. Replace IF Call by polymorphic method call
    movie.rate.warnIfNotAllowed(this.age);     
    //watch movie
  }
}

let theExorcist = new Movie(new AdultsOnlyMovieRate());
let gremlins = new Movie(new PG13MovieRate());

let jane = new Moviegoer(12);

//jane.watchMovie(theExorcist);
//Jane cannot watch the exorcist since she is 12
//jane.watchMovie(gremlins);
//Jane cannot watch gremlins since she is 12

let joe = new Moviegoer(16);

//joe.watchMovie(theExorcist);
//Joe cannot watch the exorcist since he is 16
joe.watchMovie(gremlins);
//Joe CAN watch gremlins since he is 16
``` 

With this outcome:

1- Code is polluted with **IFs**.

*We should add no more **IFs**. Extending the model will be enough.*

2- A default statement is missing.

*In this case default behavior is not needed since exceptions break flow. In many times a Null Object will be enough.*

%[https://maximilianocontieri.com/code-smell-12-null]

3- New ratings will bring new IFs.

*We will address it with polymorphic new instances.*

4- The strings representing ratings are not first class objects. A typo will introduce hard to find errors.

*This is hidden in Ratings implementation.*

5- We are forced to add getters on *Movies* to take decisions.

*We will clear this problem favoring [Demeter's Law](https://en.wikipedia.org/wiki/Law_of_Demeter).*

%[https://maximilianocontieri.com/code-smell-08-long-chains-of-collaborations]

* * *

## Breaking this collaborator chain

[//]: # (https://gist.github.com/mcsee/49382a7cff708d0b4a8c2e602295484f)

```
movie.rate.warnIfNotAllowed(this.age);  
``` 

[//]: # (https://gist.github.com/mcsee/c41292108e66a2db187151c10f690230)

```
class Movie {
  constructor(rate){
    this._rate = rate; //Rate is now private
  }
  warnIfNotAllowed(age){
    this._rate.warnIfNotAllowed(age);
  }
}

class Moviegoer {
  constructor(age){
    this.age = age;
  }
  watchMovie(movie) { 
    movie.warnIfNotAllowed(this.age);     
    //watch movie
  }
}
@mcsee

``` 

Rating is private so we don't break encapsulation.

As a consequence we are safe to avoid *getters*.

%[https://maximilianocontieri.com/nude-models-part-ii-getters]

## Applying the recipe to all IF conditions

Now we have the secret formula we can go further and try to remove the *essential* **IF condition** related to age.

[//]: # (https://gist.github.com/mcsee/884f18fcdf4e1ada78a4ad095abb5c1e)

```
class Age {
}

class AgeLessThan13 extends Age {
  assertCanWatchPG13Movie(){
    throw new Error("You are not allowed to watch this movie");    
  }
  assertCanWatchAdultMovie(){
    throw new Error("You are not allowed to watch this movie");    
  }
}

class AgeBetween13And18 extends Age {
  assertCanWatchPG13Movie(){
    //No Problem   
  }
  assertCanWatchAdultMovie(){
    throw new Error("You are not allowed to watch this movie");    
  }
}

class MovieRate {
  //If language permits this should be declared abstract
  //abstract assertCanWatch();
}

class PG13MovieRate extends MovieRate {
  //2. Move every *IF Body* to the former abstraction 
  assertCanWatch(age) {
    age.assertCanWatchPG13Movie()    
  }
}

class AdultsOnlyMovieRate extends MovieRate {
  //2. Move every *IF Body* to the former abstraction 
  assertCanWatch(age) {
     age.assertCanWatchAdultMovie()      
  }
}

class Movie {
  constructor(rate){
    this._rate = rate; //Rate is now private
  }
  watchByMe(moviegoer){
    this._rate.assertCanWatch(moviegoer.age);
  }
}

class Moviegoer {
  constructor(age){
    this.age = age;
  }
  watchMovie(movie) { 
    movie.watchByMe(this);  
  }
}

let theExorcist = new Movie(new AdultsOnlyMovieRate());
let gremlins = new Movie(new PG13MovieRate());

let jane = new Moviegoer(new AgeLessThan13());

//jane.watchMovie(theExorcist);
//Jane cannot watch the exorcist since she is 12
//jane.watchMovie(gremlins);
//Jane cannot watch gremlins since she is 12

let joe = new Moviegoer(new AgeBetween13And18());

//joe.watchMovie(theExorcist);
//Joe cannot watch the exorcist since he is 16
joe.watchMovie(gremlins);
//Joe CAN watch gremlins since he is 16

``` 

We replaced all **IFs**. In the later case using [Double Dispatch Technique](https://en.wikipedia.org/wiki/Double_dispatch)

We used our formula and it worked. But there's a smell of **over design**.

1. Classes representing Ages are not related to real concepts on our [model](https://maximilianocontieri.com/what-is-wrong-with-software).
2. The model is too complex.
3. We will need new classes related to new age groups.
4. Age groups might not be disjoint.

We should avoid the last design and set a **clear boundary** between *essential* and *accidental* ifs.

>> A Good design rule is to create abstractions if they belong to the same domain (movies and ratings) and don't do it if they cross domains (movies and ages).

## Do Ifs stink?

According to evidence shown above. We should consider many *IFs* to be a code smell and tackle them with our recipe.

%[https://sourcemaking.com/refactoring/replace-conditional-with-polymorphism]

## Why this is happening?

This article (and many others) recommend avoiding most **IF sentences**. 
This will be very hard for all developers very comfortable with its usage.

Remember, Laziness and hidden assumptions are very rooted in our profession. We have been (ab)using **IFs** for decades and our software is not the best version of it. 

This is a root cause analysis of a serious SSL defect on IOS caused by a lazy case:

%[https://blog.codecentric.de/en/2014/02/curly-braces/]

This article's thesis suggests there's a correlation between **IFs/Switch/Case** and defects.

You should give a try and avoid **IF conditionals.**

# Conclusions

With this simple technique we will be able to remove, in a procedural way, all accidental ifs.

This will make our models less coupled and more extensive.

Null object pattern is a special case of this technique. We will be able to remove all NULLs since:

> NULL ifs are always accidental.

%[https://maximilianocontieri.com/null-the-billion-dollar-mistake]

# Credits

We have been using *If removal technique* at [Universidad de Buenos Aires](https://exactas.uba.ar/) for several years. Kudos to all my fellow teachers for all the experience we gathered together with it. 
 
![Adding an else to an IF meme](https://cdn.hashnode.com/res/hashnode/image/upload/v1604269685142/ID_Gu56EQ.jpeg)

* * *

Part of the objective of this series of articles is to generate spaces for debate and discussion on software design.

%[https://maximilianocontieri.com/object-design-checklist]
