Every time I have to write a guard clause I find myself thinking a lot about what form to write it in. This might just be obsession with detail, but it’s more obsession with detail than I have with other code constructs, so I think there’s something to it. The whole point of a guard clause is readability: taking a special case and handling it quickly so you can get to the meat of the method. If we can find ways for guard clauses to more effectively disappear, our code will be better off. Ruby has a wide variety of ways you can write a guard clause, and Swift has one particular unique take. Let’s look at some examples.
Block If
Ruby:
if unexpected_condition
return_or_raise
end
Swift:
if unexpectedCondition {
returnOrThrow
}
This is the most basic form of guard clause, and works in just about every modern programming language. You immediately know you’re guarding if you see an if
block at the top of a method and inside it a statement that exits the current scope (return
, raise
/throw
, or fatalError
/preconditionFailure
). Being able to see the exiting keyword is important, and even if the condition is long, it’s easy to see because it’s on a line of its own.
This form is actually not the most common in either Ruby or Swift, because they each have more intention-revealing alternatives.
Block Unless
Ruby:
unless expected_condition
return_or_raise
end
In keeping with Ruby’s pattern of having more than one way to do things, there is an unless
keyword that functions like if !
: if the condition is false, the block is executed, and you can use this for a guard clause.
Unless blocks never seemed clear to me, for guarding or any other purpose. Reading unless
at the start of a sentence was never very clear. Plus, it sounds like the body of the block is the expected thing to be done, unless some special condition holds, and that would be the opposite of a guard clause.
But Rubyists don’t tend to do if !condition
either: they will often make a small helper method to represent the opposite of the condition. For example, if there is a complete?
predicate method, they might write the following:
def incomplete?
!complete?
end
so that you could do if incomplete?
Block Guard
guard expectedCondition else {
returnOrThrow
}
Swift has a special syntax for guard clauses. It functions like an unless
structure in Ruby: a condition is checked, and the block is executed if the condition is false. I find it much clearer to understand: “guard that this is true; if not, do this block.” The difference with the guard
keyword is that the compiler ensures you don’t allow the flow of control to continue out of its block; you need to either return a value, throw an error, or halt execution. The upshot of this is that when you see the guard
keyword you don’t need to look for the return
or throw
keyword; you already know it’s functioning as a guard clause.
Like many Swift developers, I reach for the guard
keyword first when I need a guard clause, but if the conditional needs to be negated (guard !condition
), I might prefer an if
statement (if condition
). This might be a bit surprising for other devs who might expect the guard
keyword, but if it aids readability enough, I’ll go for it.
One-line If, Unless, Guard
Ruby:
if unexpected_condition then return_or_raise end
unless expected_condition then return_or_raise end
Swift:
if unexpectedCondition { returnOrThrow }
guard expectedCondition else { returnOrRaise }
if
, unless
, and guard
constructs can all be written with their block on the same line as the conditional. Ruby blocks can always be written with either do
/`
You could technically do this even if there were multiple statements in the block, but I’ve only ever seen it done with a single statement. It’s harder to find the return
or throw statement because it’s far to the right, so I have hesitations about using the if or unless variants; but because the guard keyword ensures you exit scope, I’m much more amenable to that one.
I definitely wouldn’t use single-line format for anything long, like complex conditionals or errors with init parameters. You have to be able to see the return or throw at a glance, which means it needs to be very short.
Stylistically I’d be less likely to use a single-line guard clause in languages that require a semicolon inside the block. And I wouldn’t use these in Ruby because it has a number of other one-line constructs that are more common. This is likely because the then
/end
keywords make it more verbose than the others.
Statement Modifiers
Ruby:
return_or_raise if unexpected_condition
return_or_raise unless expected_condition
Ruby inherits a syntax from Perl where you can add an if
or unless
to the end of a statement to execute it conditionally. It effectively switches the order of a conditional, putting the check at the end. Because of this, the condition is de-emphasized, and the exceptional action is emphasized. You would think this would make it harder to tell that it was a guard clause, but because they end up starting with return
or raise
, it’s not so bad. If you see a return
or throw
keyword at the top of a method and there is code below it, you know it must be conditional, because otherwise the code below would never be executed.
Their readability is increased if you keep the value returned or thrown as simple as possible, so it’s easy to see the if or unless:
return nil if unexpected_condition
raise ArgumentError unless expected_condition
Or, even better, if you have no argument to return:
return if unexpected_condition
In this case I’m in favor of the unless
just as much as the if
: it reads naturally to me.
Low-Precedence Boolean Operators
Ruby:
expected_condition or return_or_raise
unexpected_condition and return_or_raise
This syntax is also inherited in Ruby from Perl: simple Perl scripts often tacked or die
on to the end of lines of code.
These constructs emphasize the condition rather than the response, but it’s harder to tell at a glance that a check is being performed because there is no keyword. The one thing that can help is that Ruby is so concise: often the condition is just one predicate method on self
, and the response is an empty or default value return
, or raising an error with no params. A four-word line isn’t too bad:
valid? or raise ValidationError
I would use the or
variant more likely than the and
. The expected_condition
reads like you’re stating reality, but with the unexpected condition it’s strange. Even then I would prefer conditional modifiers because the return
/raise
at the start makes them clearer.
So In Conclusion
In Ruby I prefer to use, in priority order:
return_or_raise if unexpected_condition
return_or_raise unless expected_condition
expected_condition or return_or_raise
In Swift I prefer to use, in priority order:
guard expected_condition else {
return_or_throw
}
guard expected_condition else { return_or_throw }
if unexpected_condition {
return_or_throw
}
Thanks to @rossta for some Ruby syntax corrections!