1. Retry
If at first you don't succeed…
Using a loop
tries = 0 success = false while(!success && tries < 3) puts "Trying #{tries}..." return if success tries += 1 end puts "Giving up."
Trying 0... Trying 1... Trying 2... Giving up.
Using retry
tries = 0 begin tries += 1 puts "Trying #{tries}..." raise "Didn't work" rescue retry if tries < 3 puts "I give up" end
Trying 1... Trying 2... Trying 3... I give up
More?
2. The $!
global
What the heck is $!
?
require 'English' puts $!.inspect begin raise "Oops" rescue puts $!.inspect puts $ERROR_INFO.inspect end puts $!.inspect
nil #<RuntimeError: Oops> #<RuntimeError: Oops> nil
A simple crash logger
at_exit do if $! open('crash.log', 'a') do |log| error = { :timestamp => Time.now, :message => $!.message, :backtrace => $!.backtrace, :gems => Gem.loaded_specs.inject({}){ |m, (n,s)| m.merge(n => s.version) } } YAML.dump(error, log) end end end
Nested Exceptions
require 'English' class MyError < StandardError attr_reader :original def initialize(msg, original=$ERROR_INFO) super(msg) @original = original end end
Using nested exceptions
begin begin raise "Error A" rescue => error raise MyError, "Error B" end rescue => error puts "Rescued: #{error.inspect}" puts "Original: #{error.original.inspect}" end
Rescued: #<MyError: Error B> Original: #<RuntimeError: Error A>
More?
3. Re-raising
Playing Hot Potato with exceptions.
Failures without context
data = [] open('datafile') do |f| data << eval(f.gets) until f.eof? end
SyntaxError: (eval):1:in `irb_binding': compile error (eval):1: syntax error, unexpected $undefined
Adding context
data = [] open('datafile') do |f| begin data << eval(f.gets) until f.eof? rescue Exception => e raise e, "[At #{f.path}:#{f.lineno}] #{e.message}" end end
SyntaxError: [At datafile:3] (eval):1:in `irb_binding': compile error (eval):1: syntax error, unexpected $undefined
More?
4. raise
is a method
Get out your best monkeypatching hammer!
Overriding raise
Make exceptions instantly fatal:
module RaiseExit def raise(msg_or_exc, msg=msg_or_exc, trace=caller) warn msg.to_s exit! 1 end end class Object include RaiseExit end
Hammertime
An error console for ruby: http://github.com/avdi/hammertime
require 'hammertime' raise "Oh no!"
=== Stop! Hammertime. === An error has occurred at example.rb:2:in `raise_runtime_error' The error is: #<RuntimeError: Oh no!> 1. Continue (process the exception normally) 2. Ignore (proceed without raising an exception) [...] 5. Backtrace (show the call stack leading up to the error) 6. Debug (start a debugger) 7. Console (start an IRB session)
More?
5. #exception()
Like #to_s
only for exceptions.
How you think raise
works
def raise(klass, message, backtrace) error = klass.new(message) # Nope! # ... end
How raise
really works
def raise(error_class_or_obj, message, backtrace) error = error_class_or_obj.exception(message) # ... end
.exception()
Exception.exception
Equivalent to callingException.new()
.
Exception#exception
- With no arguments, returns self.
- With a message, returns a new exception.
- With no arguments, returns self.
Implement your own #exception
class Net::HTTPResponse def exception(message="HTTP Error") RuntimeError.new("#{message}: #{code}") end end # ... response = Net::HTTP.get_response(url) raise response
exception_method.rb:11: HTTP Error: 404 (RuntimeError)
More?
6. Dynamic rescue
clauses
Getting picky about what to rescue.
rescue
and case
# case case obj when Numeric, String, NilClass, FalseClass, TrueClass puts "scalar" # ... end # rescue rescue SystemCallError, IOError, SignalException # handle exception... end
Dynamic exception lists
def ignore_exceptions(*exceptions) yield rescue *exceptions => e puts "IGNORED: '#{e}'" end puts "Doing risky operation" ignore_exceptions(IOError, SystemCallError) do open("NOTEXIST") end puts "Carrying on..."
Doing risky operation IGNORED: 'No such file or directory - NOTEXIST' Carrying on...
More?
7. Really dynamic rescue clauses
Dynamic matcher generator
def errors_matching(&block) m = Module.new (class << m; self; end).instance_eval do define_method(:===, &block) end m end
Generating a matcher dynamically
begin raise RetryableError.new("Connection timeout", 2) rescue errors_matching{|e| e.num_tries < 3} => e puts "Ignoring #{e.message}" end
More?
8. SystemExit
It's the end of the program as we know it.
exit
begin puts "Terminating program..." exit rescue Exception => e puts "Rescued: #{e.inspect}, status: #{e.status}" end puts "Continuing on..."
Terminating program... Rescued: #<SystemExit: exit>, status: 0 Continuing on...
abort
begin abort "Aborting..." rescue Exception => e puts "Rescued: #{e.inspect}, status: #{e.status}" end
Rescued: #<SystemExit: Aborting...>, status: 1
exit!
begin puts "Terminating for real" exit! rescue Exceptions => e # will never get here # no crash logger either puts "Rescued: #{e.inspect}" end
More?
9. Ignorable exceptions
LA LA LA I CAN'T HEAR YOU
A whiny method
def whiny_method puts "Before raising" raise "Pay attention to me!" puts "After raising" end
Ignoring the whiny method
begin whiny_method rescue => e puts "Ignoring '#{e}'" e.ignore end
Before raising Ignoring 'Pay attention to me!' After raising
What?
OK, maybe not out of the box.
Exception with continuation
class Exception attr_accessor :continuation def ignore continuation.call end end
Capturing the continuation
require 'continuation' # Ruby 1.9 module RaiseWithIgnore def raise(*args) callcc do |continuation| begin super rescue Exception => e e.continuation = continuation super(e) end end end end
Override system raise
class Object include RaiseWithIgnore end
Using Exception#ignore
begin whiny_method rescue => e puts "Ignoring '#{e}'" e.ignore end
Before raising Ignoring 'Pay attention to me!' After raising
More?
Just kidding.
Thank You
- Book!
- exceptionalruby.com
-
Code:
ROCKYMTNRUBY
- Rate!
- Contact!
avdi.org / @avdi
- Favor!
Tweet "Thank you @taboulichic!"