Exceptions in Ruby - Tips and Tricks


Published on

Published in: Technology

Exceptions in Ruby - Tips and Tricks

  1. 1. Exceptions in Ruby Tips & Tricks Vlad ZLOTEANU #ParisRB July 3, 2002 Software Engineer @ Dimelo @vladzloteanuCopyright Dimelo SA www.dimelo.com
  2. 2. About me R&D Software Engineer @ Dimelo @vladzloteanu https://github.com/vladzloteanuCopyright Dimelo SA www.dimelo.com
  3. 3. Exceptions (in Ruby) Failure: abnormal situation Exception: instance of Exception class (or instance of descendant) What: allows packaging info about the failure message string backtrace default behavior: program terminates (exits)Copyright Dimelo SA www.dimelo.com
  4. 4. Exceptions classes in Ruby Exception NoMemoryError ScriptError LoadError SignalException Interrupt StandardError ArgumentError IOError ... RuntimeError SystemExitCopyright Dimelo SA www.dimelo.com
  5. 5. Raising an exception class MyException < StandardError; end raise MyException.new raise # aka: raise RuntimeError.new raise “Boom!!” # aka raise RuntimeError.new(“Boom!!”) raise MyException, “Boom!!”, backtraceCopyright Dimelo SA www.dimelo.com
  6. 6. Rescuing exceptions begin raise MyError.new("Booooom!") rescue MyError puts "MyError just hapened" rescue # Will rescue StandardError puts "a RuntimeError just happened" rescue Exception => e puts ”Exception: #{e.message}” e.backtrace.each{|line| puts “ > #{line}”} endCopyright Dimelo SA www.dimelo.com
  7. 7. Intermezzo: Signals Signals – used to alert threads/processes (hardware faults, timer expiration, terminal activity, kill command, etc) SIGINT: CTRL+C in terminal (can be caught) SIGTERM: Sent by kill command by default (can be caught) SIGKILL: Uncatchable exception (kill -9) A SignalException is raised when a signal is receivedCopyright Dimelo SA www.dimelo.com
  8. 8. What should we rescue?Copyright Dimelo SA www.dimelo.com
  9. 9. Rescuing exceptions TIP: DON’T RESCUE ‘EXCEPTION’ class! begin while(true) ; end rescue Exception puts "Rescued exception; Will retry" retry end ^CRescued exception; WIll retry ^CRescued exception; WIll retry ^CRescued exception; WIll retry # Kill -9, baby!Copyright Dimelo SA www.dimelo.com
  10. 10. Rescuing exceptions (third-party libraries) # You may think this will save you from all evil .. begin Net::HTTP.get_response(’external.resource, /) rescue => e […] # Think again! # Excerpt from net/http.rb def connect s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) } Net::HTTP(Timeout library) may throw Timeout::Error < InterruptErrorCopyright Dimelo SA www.dimelo.com
  11. 11. Rescuing exceptions (third-party libraries) # Solution is easy: begin Net::HTTP.get_response(’external.resource, /) rescue StandardError, Timeout::Error => e […] Tip: Net::HTTP (and many of the gems that depend on it) can throw Timeout::Error – don’t forget to rescue itCopyright Dimelo SA www.dimelo.com
  12. 12. Rescuing exceptions - cont TIP: (rescue blocks): rescue most specific errors first, most generic last TIP: inline rescue but avoid it because: No access to rescued exception Exceptions are slow (explained later) # Will return: DefaultDecorator @decorator = “foo”.constantize rescue DefaultDecoratorCopyright Dimelo SA www.dimelo.com
  13. 13. What should we raise?Copyright Dimelo SA www.dimelo.com
  14. 14. Raising exceptions Tip: make your own exception class, do not “raise string” avoid using error message for making discriminating error type begin if @avatar_url.empty? raise “Input should not be empty” elsif @avatar_url.valid? raise “Avatar URL invalid” end rescue RuntimeError => e # They are both instances of RuntimeError, can’t # rescue only the first one endCopyright Dimelo SA www.dimelo.com
  15. 15. Raising exceptions (cont) # excerpt from OpenSSL::SSLError # - invalid certificate, invalid hostname, protocol mismatch, etc. class SSLError def initialize(message) @message = message end […] # Client code rescue OpenSSL::SSL::SSLError => e if e.message =~ /sslv3 alert unexpected message/ @conn.ssl_version="SSLv3“ retry else raiseCopyright Dimelo SA www.dimelo.com
  16. 16. Getting latest exception begin raise ”Boom!" rescue puts $!.inspect # > #<RuntimeError: Boom!> end puts $!.inspect # > nil 1 / 0 rescue nil puts $!.inspect # > nilCopyright Dimelo SA www.dimelo.com
  17. 17. Intermezzo: caller method TIP: ’caller’ gets you current execution stacktrace def foo bar end def bar puts caller.inspect end foo # [”test_caller:2:in `foo", ”test_caller.rb:9"]Copyright Dimelo SA www.dimelo.com
  18. 18. Re-raise exception – backtrace issue def foo Net::HTTP.get_response(does.not.exists, /) rescue => e # some logging, or whatever.. # raising a new error raise MyCustomException.new(‘Boom!’) end begin foo rescue => e puts e.backtrace.inspect end ["reraise_ex.rb:7:in `foo", "reraise_ex.rb:11"]Copyright Dimelo SA www.dimelo.com
  19. 19. Re-raise exception – backtrace issue (2) TIP: when reraising new exception, ensure you don’t loose the backtrace def foo Net::HTTP.get_response(does.not.exists, /) rescue => e # raising a new error, keeping old backtrace raise MyException, "And now.. boom!", e.backtrace # another option is to reraise same error: raise EndCopyright Dimelo SA www.dimelo.com
  20. 20. Ensure begin puts > always executed (even if there was normal flow’ or there raise "error in normal flow” puts "< was not flow" normal an exception) rescue good place for code cleanup puts "> RESCUE - last error is <#{$!}>” raise "error in rescue” puts "< RESCUE" ensure puts "> ENSURE - last error is <#{$!}>” raise "error in ensure” puts "< ENSURE after raising" end ruby raise_rescue.rb > normal flow > RESCUE - last error is <error in normal flow> > ENSURE - last error is <error in rescue> www.dimelo.com raise_rescue.rb:11: error in ensure (RuntimeError)Copyright Dimelo SA
  21. 21. Catch/throw ruby ‘goto label’ implementation should be used on ‘expected’ situations # Sinatra code def last_modified(time) response[Last-Modified] = time request.env[HTTP_IF_MODIFIED_SINCE] > time throw :halt, response end end def invoke res = catch(:halt) { yield } .. endCopyright Dimelo SA www.dimelo.com
  22. 22. Exceptions are slow ? https://github.com/vladzloteanu/ruby_exceptions_benchmark user_name = ““ ~ 0.3ns # Test with ‘if’ nil if user_name.empty? # Test with raise/rescue begin ~ 74 ns raise "boom!" if user_name.empty? rescue => e nil end ~ 2 ns # Test with try/catch catch(:error) do throw :error if user_name.empty? endCopyright Dimelo SA www.dimelo.com
  23. 23. Exceptions are slow ? (2) class User < ActiveRecord::Base validates :email, :presence => true end user = User.new # Test with ‘if’ ~ 1.5 ms nil unless user.save # Test with raise/rescue ~ 2 ms user.save! rescue nilCopyright Dimelo SA www.dimelo.com
  24. 24. Exceptions are slow ? (3) TIP: Exceptions should be used only on ‘exceptional’ situations Use conditional constructs or ‘throw’ for expected cases TIP: Detect errors at low level, handle them at high levelCopyright Dimelo SA www.dimelo.com
  25. 25. Recap Don’t rescue Exception! Watch out for exceptions thrown by your libs (Timeout::Error on Net::HTTP) Avoid putting error type (only) in exception message Avoid using exceptions for normal program flow Exceptions are slow(er) Catch errors on the caller code, not on the callee When you reraise, ensure that you don’t loose initial error’s backtraceCopyright Dimelo SA www.dimelo.com
  26. 26. Thank you! Questions? http://jobs.dimelo.com ;)Copyright Dimelo SA www.dimelo.com