Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Exceptional Ruby

Exceptional Ruby

A tribute to Avdi Grimm's excellent book on error handling for the YYC Ruby group.

Tim Uruski

March 09, 2017
Tweet

More Decks by Tim Uruski

Other Decks in Programming

Transcript

  1. Exceptional Ruby

  2. Tim Uruski timuruski.net

  3. Avdi Grimm virtuouscode.com

  4. • What is a failure? • How does Ruby handle

    errors? • What to do when code fails?
  5. What is a failure?

  6. None
  7. None
  8. None
  9. Human Fails Computer Fails Human Fails

  10. I realized that when a computer program had a fault,

    the machine could turn out errors millions of times faster than any human or group of humans. [...] I could actually program a machine to make more errors in a day than all human beings had made in the last 10,000 years. – Gerald M. Weinberg. “Errors.”
  11. begin do_work rescue handle_error end

  12. begin do_work rescue puts $!.message end begin do_work rescue =>

    my_error puts my_error.message end
  13. begin do_work rescue # Handle StandardError end begin do_work rescue

    ArgumentError # Handle ArgumentError end
  14. begin do_work rescue ErrorA, ErrorB # Handle ErrorA or ErrorB

    end begin do_work rescue ErrorA # Handle ErrorA rescue ErrorB # Handle ErrorB end
  15. begin do_work rescue ErrorA, ErrorB # Handle ErrorA or ErrorB

    end begin do_work rescue ErrorA, ErrorB => error # Handle ErrorA or ErrorB as error end
  16. begin do_work rescue ErrorA # Handle ErrorA rescue ErrorB #

    Handle ErrorB end
  17. parse(str) rescue 'parse failed'

  18. def example do_work rescue => error puts error.message end def

    example begin do_work rescue => error puts error.message end end
  19. begin do_work ensure cleanup end

  20. begin file = File.new('greeting.txt', 'w') file << 'Hello, world!' ensure

    file.close end
  21. def write_to(path) file = File.new(path, 'w') yield file ensure file.close

    end write_to('greeting.txt') do |file| file << 'Hello, world!' end
  22. File.open('greeting.txt', 'w') do |f| f << 'Hello, world!' end

  23. begin do_work rescue => error warn error.message ensure cleanup end

  24. begin raise 'Silent failure' ensure return end

  25. begin do_work raise end

  26. begin fail ErrorA rescue raise ErrorB end

  27. raise #=> <RuntimeError> raise RuntimeError #=> <RuntimeError> raise RuntimeError.new #=>

    <RuntimeError>
  28. raise "boom" #=> <RuntimeError "boom"> raise RuntimeError, "boom" #=> <RuntimeError

    "boom"> raise RuntimeError.new("boom") #=> <RuntimeError "boom"> raise RuntimeError.new("bada"), "boom" #=> <RuntimeError "boom">
  29. raise RuntimeError, "boom", ['./boom.rb:1'] raise RuntimeError, "boom", caller(1..10) raise RuntimeError,

    "boom", Thread.current.backtrace
  30. Raise Internals

  31. 1. Get the exception 2. Set the backtrace and cause

    3. Set global exception variable 4. Unwind the call stack
  32. RuntimeError.exception #=> <RuntimeError:0x123> <RuntimeError:0x123>.exception #=> <RuntimeError:0x123> RuntimeError.exception('boom') #=> <RuntimeError:0x123 'boom'>

    <RuntimeError:0x123 'boom'>.exception('pow') #=> <RuntimeError:0x789 'pow'>
  33. exception.set_backtrace exception.cause

  34. $! = exception require 'english' $ERROR_INFO = exception

  35. t1 = Thread.new { raise 'boom' rescue $! } t2

    = Thread.new { raise 'pow' rescue $! } puts "main: #{$!}" puts "t1: #{t1.value.inspect}" puts "t2: #{t2.value.inspect}" #=> main: #=> t1: #<RuntimeError: boom> #=> t2: #<RuntimeError: pow>
  36. uncaught.rb:2:in `boom': unhandled exception from uncaught.rb:5:in `<main>'

  37. Kernel.raise

  38. module RaiseExit
 def raise(msg_or_exc, msg=msg_or_exc, trace=caller) warn msg.to_s warn caller

    exit! end end module Kernel include RaiseExit if ENV['DEBUG'] end
  39. raise inside ensure? raise inside rescue?

  40. begin do_work rescue Timeout handle_error retry end

  41. tries = 0 begin do_work rescue TimeoutError tries += 1

    retry if tries < 3 end
  42. tries = 1 begin do_work rescue TimeoutError retry if tries

    < 3 tries += 1 # Never called... end
  43. begin do_work rescue handle_failure else handle_success end

  44. begin do_work puts 'success' rescue puts 'failure' end begin do_work

    rescue puts 'failure' else puts 'success' end
  45. begin step_1 step_2 rescue revert_step_1 # Maybe? end

  46. begin step_1 revert = false rescue revert = true end

    if revert revert_step_1 else step_2 end
  47. begin step_1 revert = false rescue revert = true end

    if revert revert_step_1 else step_2 end begin step_1 rescue revert_step_1 else step_2 end
  48. begin do_work rescue Timeout retry rescue Fatal log_failure else log_success

    ensure cleanup end
  49. Exception

  50. Exception NoMemoryError ScriptError LoadError NotImplementedError SyntaxError SecurityError SignalException Interrupt StandardError

    ArgumentError UncaughtThrowError EncodingError Encoding::CompatibilityError Encoding::ConverterNotFoundError Encoding::InvalidByteSequenceError Encoding::UndefinedConversionError FiberError IOError EOFError IndexError KeyError StopIteration ClosedQueueError LocalJumpError Math::DomainError NameError DidYouMean::Correctable NoMethodError RangeError FloatDomainError RegexpError RuntimeError SystemCallError Errno::EAGAIN IO::WaitReadable IO::EAGAINWaitReadable IO::WaitWritable IO::EAGAINWaitWritable Errno::EINPROGRESS IO::WaitReadable IO::EINPROGRESSWaitReadable IO::WaitWritable IO::EINPROGRESSWaitWritable ThreadError TypeError ZeroDivisionError SystemExit SystemStackError fatal Exception NoMemoryError ScriptError LoadError NotImplementedError SignalException StandardError ArgumentError LocalJumpError NameError NoMethodError RuntimeError SystemExit ...
  51. class MyError < Exception # ... end

  52. begin do_work rescue Exception handle_error end

  53. Responding to failures

  54. exit

  55. $ ruby -e "sleep" ^C -e:1:in `sleep': Interrupt from -e:1:in

    `<main>'
  56. begin sleep rescue Interrupt exit end

  57. begin do_work rescue exit 1 end Code = 0 ->

    Success Code = 1 -> Failure Code > 1 -> Custom Failure
  58. Output an error message

  59. begin do_work rescue => error puts error.message exit 1 end

  60. begin do_work rescue => error warn error.message exit 1 end

  61. begin do_work rescue => error abort error.message end

  62. Exception Logging Service

  63. class LogExceptions < Middlware def call(env) @app.call(env) rescue => error

    log_exception(error) end def log_exception(error) # ... end end
  64. Errbit

  65. exceptionalruby.com $15 USD $20 CAD