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