Introduction
I’ve been using Ruby for a some time now and I’ve noticed that early returns can be over used. I’ll review at one example with enums where I think some early returns can be harmful.
Note: I am not saying early returns are bad, but in this specific example they can be harmful.
Enums
Ruby does not natively support enums, but they still do exist in business and other domains. There are alternative ways to define them, let’s look at the simplest one:
module UserRole
ADMIN = :admin
USER = :user
end
Simple module with two constants. This is a very simple enum, but it can be used in a lot of places. In a business domain it also shows us possible values for a user role.
Working with enums
Pattern matching
Let’s write a simple function which greets the user based on his role:
def greet(role)
case role
in UserRole::ADMIN
puts "Hello, Admin!"
in UserRole::USER
puts "Hello, User!"
end
end
greet(UserRole::ADMIN) # => Hello, Admin!
Quite concise, powerful and easy to read.
But what if we want to add a new role? Let’s say MODERATOR
.
module UserRole
ADMIN = :admin
USER = :user
MODERATOR = :moderator
end
Now let’s try greeting a moderator:
greet(UserRole::MODERATOR) # => NoMatchingPatternError
Wonderful, we get an error. This is a good thing, because we know that we need to update our function.
Old switch statement
What if we tried the same, just with case
and when
instead of in
?
def greet(role)
case role
when UserRole::ADMIN
puts "Hello, Admin!"
when UserRole::USER
puts "Hello, User!"
end
end
greet(UserRole::MODERATOR) # will fail silently
This is not good, because we don’t get any errors and we don’t know that we need to update our function.
However, we can use else
to get an error:
def greet(role)
case role
when UserRole::ADMIN
puts "Hello, Admin!"
when UserRole::USER
puts "Hello, User!"
else
raise "Unknown role"
end
end
This is better, but we do need discipline to use else
every time. On top of that
we need specifically create an error, which is not very convenient.
NoMatchinPatternError before was quite descriptive itself.
Early returns
Let’s try to use early returns:
def greet(role)
return puts "Hello, Admin!" if role == UserRole::ADMIN
puts "Hello, User!" if role == UserRole::USER
end
greet(UserRole::MODERATOR) # will fail silently
It is the problem – this will fail silently. Also, there is no else
to save us.
We don’t get any errors and we don’t know that we need to update our function. As bad as it gets.
Conclusion
Early returns can be harmful, because they can hide errors and make code more prone to bugs. They can also help make functions simpler, but its not always the case.
I think that pattern matching [1] is the best way to work with data structures similar to enums.
References
[1] - https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rdoc.html