There is still the prejudice, that a method/ function generally should have only one exit point. Thus multiple exit points are considered as bad practice.
The method warranty_days stands for an example with a single exit point (at the end). It calculates the number of remaining warranty in days:
class Product
attr_accessor :sold_at
DEFAULT_WARRANTY_DAYS = 180
SECONDS_PER_DAY = 24 * 60 * 60
def warranty_days
if sold_at.nil?
DEFAULT_WARRANTY_DAYS
else
days_since_sold < DEFAULT_WARRANTY_DAYS ? (DEFAULT_WARRANTY_DAYS - days_since_sold) : 0
end
end
private
def days_since_sold
((Time.now - sold_at).to_f / SECONDS_PER_DAY).round
end
end
However the opinion dates back to the old C/ C++ days. Back then memory was allocated at beginning of the procedure and deallocated at the end. There was a chance of missing deallocation, having multiple exit points within the procedure.
Modern languages (e.g. Ruby) include a garbage collection, that cares about that. That is why the argument is obsolete.
The so called Guard Clause Pattern stands in contrast to it. The guards job is to secure following logic within the method. Reasons are unexpected passed parameters or inappropriate object states.
The example once again, but with 2 guards:
class Product
attr_accessor :sold_at
DEFAULT_WARRANTY_DAYS = 180
SECONDS_PER_DAY = 24 * 60 * 60
def warranty_days
return DEFAULT_WARRANTY_DAYS if sold_at.nil? # 1. guard
return 0 if days_since_sold > DEFAULT_WARRANTY_DAYS # 2. guard
DEFAULT_WARRANTY_DAYS - days_since_sold
end
private
def days_since_sold
((Time.now - sold_at).to_f / SECONDS_PER_DAY).round
end
end
They even improve the readability in such simple example. Debugging also is easier, because it is necessarily not required to read the entire code for a certain debug path.
The so called early returns also allow to break nested if/else conditions into single if conditions.
By means of short if conditions the logic can be moved into the first indentation level and core logic gains attention.
Hence even ternary operators are tended to be used less (see the examples second guard).
Also Or constraints can be reduced by early returns.
The scenario for an example is about examining creditworthiness in a bank. To begin with the original method:
def creditworthy? person
customers.include?(person) || (person.adult? && person.country_citizen?)
end
The Guard Clause Pattern alternative:
def creditworthy? person
return true if customers.include?(person)
person.adult? && person.country_citizen?
end