Improve code readability with conditional safeguards
Tags: codequality
Published: 2019-10-15 08:44:50

> "You shall not pass!" - Gandalf the Grey, 2001

#TL;DR

1. Define what your code supposed to do.
2. Write safeguards. One safeguard should define one specific condition.
3. Don't use `else`, if able.
4. Don't nest `if`, if able.

-----

Conditional is one of basic fundamentals in programming. And the most basic of many kind of conditionals in programming, is `if` statement.

Writing an `if` statement is easy, but if we are not careful, it can easily leads to giant spaghetti code mess, hard to read and debug.

I think, there are (not limited to) two things can lead to messy codes:

1. Overnesting `if` statements
2. Unnecessary `else` statements

You might say, *"Just write a comment to explain!"* 

Well, comments are great and can be helpful. But they should not be used to justify a messy code.

So this time, I'm going to share with you, about how I usually write my conditionals.

I don't know what the official name of it, LOL. But I think I first read it somewhere as **SAFEGUARDING**.

-----

#CASE STUDY

To do this, first, we need to determine what our function should do. To make it easier to explain, let's jump to code: 

*(Code is in Typescript)*


Say we want to add a polymorph skill to our game. Therefore, we write this code:

```
export const polymorph = (caster: Person, target: Person) : Beast => {
  return new Beast(from: target);
}
```

Plain and simple. Now our target is polymorph-ed if we call the function.

But wait, not everyone should be able to cast polymorph! It will be broken if a knight is able to cast it.

So, let's **safeguard** it.

```
export const polymorph = (caster: Person, target: Person) : Beast => {
  try {
    if(caster.class !== 'WIZARD') {
      throw new Error('Only wizard can cast polymorph!');
    }

    return new Beast(from: target);
  } catch (error) {
      throw error;
  }
}
```


There you go! Now, only wizards can cast polymorph. 


Hmm. 

Btw, aren't wizards need 100 mana to cast polymorph? Well, let's add the mana cost to the logic.


```
export const polymorph = (caster: Person, target: Person) : Beast => {
  try {
    if(caster.class !== 'WIZARD' && caster.mana < 100) {
      
      if(caster.mana < 100) {
        throw new Error('Not enough mana!');
      }

      throw new Error('Only wizard can cast polymorph!');
    }
    
    return new Beast(from: target);
  } catch (error) {
      throw error;
  }
}
```

Umm. Nested `if` statement. Can we un-nest it?


```
export const polymorph = (caster: Person, target: Person) : Beast => {
  try {
    if(caster.class !== 'WIZARD') {
      throw new Error('Only wizard can cast polymorph!');
    }

    if(caster.mana < 100) {
      throw new Error('Not enough mana!');
    }

    return new Beast(from: target);
  } catch (error) {
      throw error;
  }
}
```

Ah nice. 

I think it's better. Improved readability by separating each safeguard's concern.

Wait. How if our target is currently immune?

Alright, let's add the logic to our function.


```
export const polymorph = (caster: Person, target: Person) : Beast => {
  try {
    if(caster.class !== 'WIZARD') {
      throw new Error('Only wizard can cast polymorph!');
    }

    if(caster.mana < 100) {
      throw new Error('Not enough mana!');
    }

    if(target.isImmune) {
      throw new Error('Target is immune!');
    }

    return new Beast(from: target);
  } catch (error) {
      throw error;
  }
}
```

There you go, now polymorph can only cast by wizards, requires 100 mana, and cannot pierce immune targets.

And now, both of our players as well as the code maintainers are happy.

-----

#SUMMARY

1. Define what your code supposed to do. *(It'll be better if we separate the concern of each functions)*
2. Write safeguards. One safeguard should define one specific condition.
3. Don't use `else`, if able.
4. Don't nest `if`, if able.

-----

#CLOSING

What I really like about safeguard, is because it improve code readability and easier to define structured error messages. 

It's like saying: *"Ok, this is what the code should do. But, look at the safeguards! They prevents the code to function under wrong circumstances."*

Oh, before anyone fire out their gun, I'm not against `else` statements. It's necessary sometimes. After all, it exists for a reason. But in some cases where we can omit it and improve code readability, why even bother to write it?

**Less is more**

-----

#NOTE

For you who don't know, `throw`-ing an error means stopping the function from continuing. It's similar like writing `return;`.