Exceptions

  • Signal exceptions using the fail method. Use raise only when catching an exception and re-raising it (because here you're not failing, but explicitly and purposefully raising an exception). [link]

    begin
      fail "Oops"
    rescue => error
      raise if error.message != "Oops"
    end
    
  • Don't specify RuntimeError explicitly in the two argument version of fail/raise. [link]

    # bad
    fail RuntimeError, "message"
    
    # good:  signals a RuntimeError by default
    fail "message"
    
  • Prefer supplying an exception class and a message as two separate arguments to fail/raise, instead of an exception instance. [link]

    # bad
    fail SomeException.new("message")
    # Note that there is no way to do `fail SomeException.new("message"), backtrace`.
    
    # good
    fail SomeException, "message"
    # Consistent with `fail SomeException, "message", backtrace`.
    
  • Do not return from an ensure block. If you explicitly return from a method inside an ensure 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. [link]

    def foo
      fail
    ensure
      return "very bad idea"
    end
    
  • Use implicit begin blocks where possible. [link]

    # 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). [link]

    # 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 }
    
  • Don't suppress exceptions. [link]

    # 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. An exception can be made for assignment of the error. [link]

    # 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
    
    # passable:  assigning the error
    content_or_error = read_file rescue $!
    
  • Don't use exceptions for flow of control. [link]

    # 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 to exit, requiring you to kill -9 the process. [link]

    # 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
    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. [link]

    # 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. [link]

    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. [link]

    # bad:  you need to close the file descriptor explicitly
    f = File.open("testfile")
      # ...
    f.close
    
    # good:  the file descriptor is closed automatically
    File.open("testfile") do |f|
      # ...
    end