Why Guard Statement is Bae
Guard statements solve problems like the Pyramid of Doom and stuffing a lot of conditions onto one if conditional which may cause a lot of confusion when a bug or an error shows up. Guards also makes validating data safer!
This past summer I was given the task of debugging a program that uses guard statements. I thought to myself “what the hell are guard statements?”. Curious, I googled to learn more about them. At first, I was confused on why you would ever use it but as I started developing using Swift, I saw the benefits they had and how bae they are.
According to Apple’s The Swift Programming Language, a guard statement is used to transfer program control out of a scope if one or more conditions are not met. Basically you can think of them like if statements but they exit gracefully.
This how the guard statement looks like in Swift 3:
guard condition else {
statement
return/break/continue/throw //Use one of them
}
Unlike if statements, guard statements do more than helping us exit the program early and safely. It validates data while making our code cleaner and easier to read. If we are dealing with optionals, guard statements will safely unwrap them for us.
To understand bae and its benefits better, let’s run through some examples:
If Conditionals:
Let’s say we have a Pokedex which contains all of the Pokemons we have seen and captured. I am too lazy to manually count how many I encountered, so I decided to write this program to check it for us.
Using if statements:
//Example 1
func checkPokeDex(pokedex: [String]?) {
if let pokedex = pokedex, pokedex.count < 151 {
print("You have not seen enough Pokemons yet")
return
}
}
It works, but let’s imagine if this function was more complex by adding a few more parameters:
//Example 2 (imagining Example 1 as more complex)
func checkPokeDex(pokedex1: [String]?, pokedex2: [String]?, pokedex3: [String]?, pokedex4: [String]?, pokedex5: [String]?) {
if let pokedex1 = pokedex1, pokedex1.count < 151 && let pokedex2 = pokedex2, pokedex2.count < 251 && let pokedex3 = pokedex3, pokedex3.count < 202 && let pokedex4 = pokedex4, pokedex4.count < 151 && let pokedex5 = pokedex5, pokedex5.count < 155 {
print("You have not seen enough Pokemons yet")
return
}
}
The function works, however there is one flaw with it:
The more complex we make this function, the more confusing it will become before it runs your statements.
Using guard statements, we can solve this flaw! Revising Example 1, our code would now look like:
//Revised Example 1
func checkPokeDex(pokedex: [String]?) {
guard let pokedex = pokedex, pokedex.count >= 151 else {
print("You have not seen enough Pokemons yet")
return
}
print("You have seen over 151 Pokemons!")
}
We are now checking for the wanted statement, where if we encounter exactly or over 151 Pokemons, it would return early and safely. Even if we made this function more complex, the guard statements would allow us to exit early and cleanly.
Pyramid of Doom:
Now let’s look at the Pyramid of Doom. The Pyramid of Doom is when you have too many nested statements within a function.
FUNCTION
- if statement
-- if statement
--- if statement
----//does somethingand so on
As you can see above, the more nested the function gets, the more conditions you must past before reaching your statement. The guard statement breaks this pyramid and makes it cleaner to use.
Let’s look at this example:
Let’s say university x offer students the chance to go on exchange. The requirements to qualify for exchange are as followed:
- Must be at least 20 years of age
- Must have a minimum GPA of 3.0
- Currently must be at least in their second year of study
//Example 3
func qualifyForExchange(age: Int?, gpa: Double?, year: Int?) {
if let age = age, age >= 20 {
if let gpa = gpa, gpa >= 3.0 {
if let year = year, year >= 2 {
//do something
} else {
//do something
}
} else {
//do something
}
} else {
//do something
}
}
As you can see, the function is very nested and it has to pass many conditions; some unwanted. Let’s say we have a person who’s 21 years old, with a 3.1 GPA and is in their first year of study. The first two conditions will pass, but the last one will not. The program unfortunately did not return early when it failed. Fortunately, we can fix this problem by breaking down the nested levels to use guard statements. Within this example, you can see we are dealing with an optional Int. As stated earlier, guard can unwrap the optional and that value will stay unwrapped. This will make our code cleaner and more readable.
//Revised Example 3
func qualityForExchange(age: Int?, gpa: Double?, year: Int?) {
guard let age = age, age >= 20 else {
//do something
return
}
guard let gpa = gpa, gpa >= 3.0 else {
//do something
return
}
guard let year = year, year >= 2 else {
//do something
return
}
print("You qualify!")
}
If you do not meet any of the conditions, it will return early and safely without having to check the rest of the statements. If you qualify, it will print out the “You qualify!” statement. This is a lot cleaner and nicer to have than having a pyramid.
To summarize, the guard statement is really useful for validating data where it will fail early if it does not meet the condition. This allows the program to exit early and safely. It is a great statement to know especially within iOS Development which is why I say it is bae!
Correction: Thanks to Kenneth Trueman for correcting an error in Example 3.