06 - Exceptions
# bad
fail SomeException, 'message'
# good
raise SomeException, 'message'
Don't specify
RuntimeError
explicitly in the two argument version ofraise
.# bad raise RuntimeError, 'message' # good - signals a RuntimeError by default raise 'message'
Prefer supplying an exception class and a message as two separate arguments to
raise
, instead of an exception instance.# bad raise SomeException.new('message') # Note that there is no way to do `raise SomeException.new('message'), backtrace`. # good raise SomeException, 'message' # Consistent with `raise SomeException, 'message', backtrace`.
Do not return from an
ensure
block. If you explicitly return from a method inside anensure
block, the return will take precedence over any exception being raised, and the method will return as if no exception had been raised at all. In effect, the exception will be silently thrown away.# bad def foo raise ensure return 'very bad idea' end
Use implicit begin blocks where possible.
# bad def foo begin # main logic goes here rescue # failure handling goes here end end # good def foo # main logic goes here rescue # failure handling goes here end
Mitigate the proliferation of
begin
blocks by using contingency methods (a term coined by Avdi Grimm).# bad begin something_that_might_fail rescue IOError # handle IOError end begin something_else_that_might_fail rescue IOError # handle IOError end # good def with_io_error_handling yield rescue IOError # handle IOError end with_io_error_handling { something_that_might_fail } with_io_error_handling { something_else_that_might_fail }
-
# bad begin # an exception occurs here rescue SomeError # the rescue clause does absolutely nothing end # bad do_something rescue nil
Avoid using
rescue
in its modifier form.# bad - this catches exceptions of StandardError class and its descendant classes read_file rescue handle_error($!) # good - this catches only the exceptions of Errno::ENOENT class and its descendant classes def foo read_file rescue Errno::ENOENT => ex handle_error(ex) end
Don't use exceptions for flow of control.
# bad begin n / d rescue ZeroDivisionError puts 'Cannot divide by 0!' end # good if d.zero? puts 'Cannot divide by 0!' else n / d end
Avoid rescuing the
Exception
class. This will trap signals and calls toexit
, requiring you tokill -9
the process.# bad begin # calls to exit and kill signals will be caught (except kill -9) exit rescue Exception puts "you didn't really want to exit, right?" # exception handling end # good begin # a blind rescue rescues from StandardError, not Exception as many # programmers assume. rescue => e # exception handling end # also good begin # an exception occurs here rescue StandardError => e # exception handling end
Put more specific exceptions higher up the rescue chain, otherwise they'll never be rescued from.
# bad begin # some code rescue StandardError => e # some handling rescue IOError => e # some handling that will never be executed end # good begin # some code rescue IOError => e # some handling rescue StandardError => e # some handling end
Release external resources obtained by your program in an
ensure
block.f = File.open('testfile') begin # .. process rescue # .. handle error ensure f.close if f end
Use versions of resource obtaining methods that do automatic resource cleanup when possible.
# bad - you need to close the file descriptor explicitly f = File.open('testfile') # some action on the file f.close # good - the file descriptor is closed automatically File.open('testfile') do |f| # some action on the file end
Favor the use of exceptions from the standard library over introducing new exception classes.