Guard Clauses vs. Nested Conditionals
An explanation of Ruby guard clauses how to use them as an alternative to nested if statements
TLDR; a guard clause is a premature return (early exit) that "guards" against the rest of your code from executing if it's not necessary (based on criteria you specify).
Soon after I started my career as a Ruby on Rails developer I learned about guard clauses and how they can improve code readability.
Let's take a look at an example method with a standard if/else
statement.
def example_method(parameter)
if parameter == true
# awesome code here
else
return nil
end
end
In this method we're doing something that's common: evaluating a parameter and:
- executing some code if our expectations are met OR
- executing some other code if our expectations are not met.
In this case:
- if the parameter is
true
we execute some "awesome code" - if not, we just return
nil
This method can be shortened to only two lines of code by using a guard clause.
def example_method(parameter)
return nil unless parameter == true #<-- guard clause
# awesome code here
end
This was as short example to illustrate the concept of a guard clause. Now let's look at an example where a guard clause can help eliminate a code smell: nested conditionals.
def print_shipping_label(order)
if order.outstanding_payments?
nil
else
if order.address_incomplete?
order.send_address_reminder
else
if order.standard_shipping?
order.print_standard_shipping_label
else
order.print_priority_shipping_lable
end
end
end
end
This code is hard to read and follow what's happening in my opinion (just look at how many levels of indention are in the nested if/else
statements!)
Refactoring Nested Conditionals Using Guard Clauses
We can use guard clauses to refactor the nested if/else
statements and make the method more readable and easier to follow.
def print_shipping_label2(order)
return nil if order.outstanding_payments?
return order.send_address_reminder if order.address_incomplete?
return order.print_standard_shipping_label if order.standard_shipping?
order.print_priority_shipping_label
end
I like guard clauses for these reasons, but you should use them where it makes sense for you and your team.
For example, some languages require explicit resource management and having a single exit point from a method would make sense to prevent memory leaks. However Ruby has garbge collection to automatically collect unused objects.
Takeaway:
Guard clauses guard (protect) the rest of your code from executing if not necessary (based on certain criteria) and are placed at the top of a method.