Guard clauses and modifiers
return unless
A guard clause handles the boring case at the top of a method and leaves the rest of the body for the real work. Ruby reads it as one line, and the reader stops worrying about the edge case after the first line:
def double(x)
return :none if x.nil?
x * 2
end
puts double(2)
ruby -e 'def f(x) = (return :none if x.nil?; x * 2); puts f(2)'
4
unless is the negative form, and return can carry a value: return [] unless File.exist?(path) is the same guard with the empty answer in it.
next if in a loop
The same idea works inside a loop. next skips the rest of the block for one item, and break leaves the loop:
ruby -e '[1, 2, 3, 4].each { |n| next if n.even?; print n }; puts'
13
The modifier form is the idiom: a condition at the end of a one-line body reads as "do this, except when". A block with two guards at the top is usually a filter that should have been select.
A method rewritten with guards
The nested version is what a guard clause replaces. Both methods answer the same thing; the second one has no indentation to follow:
def check(x)
if x.nil?
:none
else
if x < 0
:neg
else
x
end
end
end
def check(x)
return :none if x.nil?
return :neg if x < 0
x
end
ruby -e 'def check(x); return :none if x.nil?; return :neg if x < 0; x; end; puts check(-1); puts check(2)'
neg
2
Two guards and one return line is about the limit. After three, the conditions are the method, and a small lookup or a case says it better.
What to watch for
A modifier changes the flow quietly: puts x if x
hides a branch where a reader expects a statement. Keep the modifier for
one condition and no else; write the full
if when the body has more than one line.
In a method that returns a value, the guard returns early and the body
returns last. A modifier on the last expression
(x * 2 if x) returns
nil when the condition is false, which is easy to
miss.
Next: break, next, redo and retry
Article author: Arthur Isaev