In this post, I'd like to share with you some basic knowledge and tips about exception handling in Ruby.
Avoiding rescue with Exception
One common mistake when handling exceptions in Ruby is catching exceptions with an Exception
instance, e.g rescue Exception => e
.
The reason is very simple, let take a look at the Ruby Exception Hierarchy below:
As you see, Exception
is the root of all exception / error classes in Ruby, which also includes NoMemoryError
, SyntaxError
, Interrupt
, etc.
By rescuing with Exception
, you may inadvertently allow code with wrong syntax being deployed, or prevent user terminating your program in normal way, like the example below:
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e.class}! ohnoes!"
end
end
When running this in console, users are unable to stop it by using CTRL + C
.
Therefore, you should use StandardError
instead when catching general exceptions, which also have a shorthand syntax rescue => e
.
You can check this blog post for better understanding about this bad practice.
Custom Exception
We can define a custom exception class that inherit from the base class Exception
, but usually we should inherit from StandardError
or its subclasses:
class MyCustomError < StandardError; end
We can also add additional attributes in our custom exception class to provide more context when handling the exception:
class MyCustomError < StandardError
attr_reader :object
def initialize(object)
@object = object
end
end
# usage
begin
raise MyCustomError.new(user), 'Something broke'
rescue MyCustomError => e
e.object # => user
end
raise / rescue synonyms
Following are some common uses of raise / rescue
with their synonymous forms which can explain them a little bit clearer.
fail
# is equivalent to
raise
raise MyCustomError, 'Something broke'
# is equivalent to
raise MyCustomError.new('Something broke')
raise
# is equivalent to
raise RuntimeError
raise 'Something broke'
# is equivalent to
raise RuntimeError, 'Something broke'
begin
rescue => e
end
# is equivalent to
begin
rescue StandardError => e
end
raise / rescue vs throw / catch
The difference between raise / rescue
and throw / catch
has caused much confusion for ruby beginners, especially for those who came from Java or C++ background.
While raise / rescue
is purely exception handling stuff for exceptional circumstances, throw / catch
allow you to quick jump out of a block define by a specific symbol.
It come very handy if you want to break out of nested loops, for example:
found_person = nil
catch(:found) do
people.each do |person|
person.cars.each do |car|
if car.name == 'Toyota Camry'
found_person = person
throw :found
end
end
end
end
In this example, we try to find the first person who own a Toyota Camry by looping through all cars of each person and stop right after we find the first car that matched.
throw
also accept a return value beside the identifying symbol, so the previous example could be rewritten as:
found_person = catch(:found) do
people.each do |person|
person.cars.each do |car|
throw(:found, person) if car.name == 'Toyota Camry'
end
end
end
Shorthand syntax to rescue a method
If you have some method like this:
def some_method
begin
do_something!
rescue
# handle exception
end
end
You can make it a little bit cleaner by rewriting it as following:
def some_method
do_something!
rescue
# handle exception
end