Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Code Fast, die() Early, Throw Structured Exceptions

3,614 views

Published on

Slides from a short talk given at January 2012 DC.pm. Covers "classic" exceptions in Perl as well as some libraries to make working with exceptions easier.

Published in: Technology, Art & Photos
  • Be the first to comment

Code Fast, die() Early, Throw Structured Exceptions

  1. 1. Live Fast, Die Young,Have A Good Looking Corpse
  2. 2. Code Fast, die() Early,Throw Structured Exceptions
  3. 3. Throw Structured Exceptions John SJ Anderson @genehack 03 Jan 2012
  4. 4. “Classic” Perl exception throwing
  5. 5. “Classic” Perl exception throwing• Throw an exception with die()
  6. 6. “Classic” Perl exception throwing• Throw an exception with die()• Or Carp::croak(), Carp::confess(), etc.
  7. 7. “Classic” Perl exception throwing• Throw an exception with die()• Or Carp::croak(), Carp::confess(), etc.• TIMTOWTDI!
  8. 8. “Classic” Perl exception throwing• Throw an exception with die()• Or Carp::croak(), Carp::confess(), etc.• TIMTOWTDI!• Catch an exception with eval {}
  9. 9. “Classic” Perl exception throwing• Throw an exception with die()• Or Carp::croak(), Carp::confess(), etc.• TIMTOWTDI!• Catch an exception with eval {}• Handle an exception by looking at $@
  10. 10. “Classic” Perl exception throwing 1 #! /usr/bin/perl 2 3 use strict; 4 use warnings; 5 6 eval { my $result = this_might_fail() }; 7 8 if( $@ ) { 9 # handle the error here10 }1112 sub this_might_fail {13 die "FAILED!"14 if rand() < 0.5;15 }
  11. 11. Problems with “classic” Perl exceptions
  12. 12. Problems with “classic” Perl exceptions• $@ can get clobbered
  13. 13. Problems with “classic” Perl exceptions• $@ can get clobbered• $@ can get clobbered by code you don’t own
  14. 14. Problems with “classic” Perl exceptions• $@ can get clobbered• $@ can get clobbered by code you don’t own• $@ might be a false value
  15. 15. Problems with “classic” Perl exceptions• $@ can get clobbered• $@ can get clobbered by code you don’t own• $@ might be a false value• If $@ is a string, you’re depending on duplicated information, which will break.
  16. 16. Use Try::Tiny for“semi-modern” Perl exceptions
  17. 17. Use Try::Tiny for “semi-modern” Perl exceptions• Provides try{}/catch{}/finally{} blocks
  18. 18. Use Try::Tiny for “semi-modern” Perl exceptions• Provides try{}/catch{}/finally{} blocks• Handles details of properly dealing with complexities around $@
  19. 19. Use Try::Tiny for “semi-modern” Perl exceptions• Provides try{}/catch{}/finally{} blocks• Handles details of properly dealing with complexities around $@• Lightweight and generally Just Works(tm).
  20. 20. Use Try::Tiny for “semi-modern” Perl exceptions• Provides try{}/catch{}/finally{} blocks• Handles details of properly dealing with complexities around $@• Lightweight and generally Just Works(tm).• N.b.: you have to end try{}/catch{} with a semicolon. Don’t forget this!
  21. 21. Use Try::Tiny for “semi-modern” Perl exceptions 1 #! /usr/bin/perl 2 3 use strict; 4 use warnings; 5 6 use Try::Tiny; 7 8 try { 9 my $result = this_might_fail();10 }11 catch {12 # handle the error here13 };1415 sub this_might_fail {16 die "FAILED!"17 if rand() < 0.5;18 }
  22. 22. Problems with “semi-modern” Perl exceptions• $@ can get clobbered• $@ can get clobbered by code you don’t own• $@ might be a false value• If $@ is a string, you’re depending on duplicated information, which will break.
  23. 23. Problems with “semi-modern” Perl exceptions• If $@ is a string, you’re depending on duplicated information, which will break.
  24. 24. Wait, where’s the duplicated information? 1 my $answer; 2 try { 3 # any of these might throw an exception 4 my $this = this_might_fail(); 5 my $that = something_else_might_fail(); 6 $answer = combine_them( $this , $that ); 7 } 8 catch { 9 # our error is in $_10 if( $_ =~ /some error/ ) {11 # handle some error12 }13 elsif( $_ =~ /another error/ ) {14 # handle another error15 }16 else { # not sure what the problem is, just give up17 confess( $_ );18 }19 };
  25. 25. Wait, where’s the duplicated information? 1 my $answer; 2 try { 3 # any of these might throw an exception 4 my $this = this_might_fail(); 5 my $that = something_else_might_fail(); 6 $answer = combine_them( $this , $that ); 7 } 8 catch { 9 # our error is in $_10 if( $_ =~ /some error/ ) {11 # handle some error12 }13 elsif( $_ =~ /another error/ ) {14 # handle another error15 }16 else { # not sure what the problem is, just give up17 confess( $_ );18 }19 };
  26. 26. Wait, where’s the duplicated information?• As soon as somebody “fixes” the string in some die() somewhere, you’ve potentially broken exception handling
  27. 27. Wait, where’s the duplicated information?• As soon as somebody “fixes” the string in some die() somewhere, you’ve potentially broken exception handling• And you can’t even easily tell where, because it’s probably in a regexp that doesn’t look at all like the changed string
  28. 28. Wait, where’s the duplicated information?• Even if you have tests for the code in question, do you really have test coverage on all your exception cases?
  29. 29. Wait, where’s the duplicated information?• Even if you have tests for the code in question, do you really have test coverage on all your exception cases?• (Almost certainly not. If you do, come write tests for $WORK_PROJECT, we need the help...)
  30. 30. So what’s the solution?
  31. 31. So what’s the solution?• die() can also take a reference as an argument
  32. 32. So what’s the solution?• die() can also take a reference as an argument• So you can die() with an object!
  33. 33. So what’s the solution?• die() can also take a reference as an argument• So you can die() with an object!• Which means you can cram all sorts of useful information into your exceptions
  34. 34. So what’s the solution?• die() can also take a reference as an argument• So you can die() with an object!• Which means you can cram all sorts of useful information into your exceptions• And more importantly, handle them in a structured fashion that’s much less brittle than string comparisons
  35. 35. A framework for structured exceptions: Exception::Classuse Exception::Class (    MyException,     AnotherException => { isa => MyException },     YetAnotherException => {        isa         => AnotherException,        description => These exceptions are related to IPC    },     ExceptionWithFields => {        isa    => YetAnotherException,        fields => [ grandiosity, quixotic ],    },);
  36. 36. A framework for structured exceptions: Exception::Class# tryeval { MyException->throw( error => I feel funny. ) }; my $e; # catchif ( $e = Exception::Class->caught(MyException) ) {    warn $e->error, "n", $e->trace->as_string, "n";    warn join , $e->euid, $e->egid, $e->uid, $e->gid, $e->pid;    exit;}elsif ( $e = Exception::Class->caught(ExceptionWithFields) ) {    $e->quixotic ? do_something_wacky() : do_something_sane();}else {    $e = Exception::Class->caught();    ref $e ? $e->rethrow : die $e;}
  37. 37. Exception::Class Pros
  38. 38. Exception::Class Pros• Nice declarative syntax
  39. 39. Exception::Class Pros• Nice declarative syntax• Possible to declare detailed or simple exception class hierarchies very simply
  40. 40. Exception::Class Pros• Nice declarative syntax• Possible to declare detailed or simple exception class hierarchies very simply• Supports macro definitions to make throwing particular exception types easier
  41. 41. Exception::Class Cons
  42. 42. Exception::Class Cons• Not really designed for use with Try::Tiny
  43. 43. Exception::Class Cons• Not really designed for use with Try::Tiny• Based on Class::Data::Inheritable, not Moose
  44. 44. A Moose role for structured exceptions: Throwablepackage Redirect;use Moose;with Throwable; has url => (is => ro);...then later...Redirect->throw({ url => $url });
  45. 45. Throwable Pros
  46. 46. Throwable Pros• Implemented as a Moose role, so your exception classes are just normal Moose classes that consume the role
  47. 47. Throwable Pros• Implemented as a Moose role, so your exception classes are just normal Moose classes that consume the role• So you get the usual Moose-y good stuff around attributes and methods and such.
  48. 48. Throwable Pros• Implemented as a Moose role, so your exception classes are just normal Moose classes that consume the role• So you get the usual Moose-y good stuff around attributes and methods and such.• Comes with a grab-bag of typical exception behaviors (in Throwable::X), like stack traces, printf-ish messages, etc.
  49. 49. Throwable Cons
  50. 50. Throwable Cons ?
  51. 51. Throwable Cons ? So far, I haven’t really found any.
  52. 52. Throwable Cons ? So far, I haven’t really found any.(Of course, that doesn’t mean there aren’t any...)
  53. 53. Error HandlingPatterns & Anti-Patterns
  54. 54. Error Handling Patterns & Anti-Patterns• DO use exceptions instead of error flags
  55. 55. Error Handling Patterns & Anti-Patterns• DO use exceptions instead of error flags• DO throw exceptions as early as possible
  56. 56. Error Handling Patterns & Anti-Patterns• DO use exceptions instead of error flags• DO throw exceptions as early as possible• DON’T catch exceptions unless you’re going to handle them – just let them propagate upwards
  57. 57. Error Handling Patterns & Anti-Patterns• DO use exceptions instead of error flags• DO throw exceptions as early as possible• DON’T catch exceptions unless you’re going to handle them – just let them propagate upwards• DO design your web application-level error actions to handle your business logic-level exceptions
  58. 58. Use exceptions instead of error flags 1 sub some_catalyst_action :Local { 2 my( $self , $c ) = @_; 3 my $session = get_huge_session_object( $c->session ); 4 5 my $validated_params = validate_request_params( $c->request->params ) 6 or $c->detach( error ); 7 8 my $step_one_result = $c->model(BusinessLogic)->do_step_one( $session , $validated_params ); 9 $c->detach( error ) if $session->has_error();1011 my $step_two_result = $c->model(BusinessLogic)->do_step_two( $step_one_result, $session );12 $c->detach( error ) if $session->has_error();1314 $c->stash({15 one => $step_one_result ,16 two => $step_two_result ,17 });18 }
  59. 59. Use exceptions instead of error flags Please please please don’t write code like this!
  60. 60. Use exceptions instead of error flags
  61. 61. Use exceptions instead of error flags• Forget just one of those checks and you’ve got a hard to track down bug
  62. 62. Use exceptions instead of error flags• Forget just one of those checks and you’ve got a hard to track down bug• Many times, $session was passed just to provide access to that error flag. Far too much information was being passed around for no reason
  63. 63. Use exceptions instead of error flags• Forget just one of those checks and you’ve got a hard to track down bug• Many times, $session was passed just to provide access to that error flag. Far too much information was being passed around for no reason• The error action gets no real info about what the problem was, or it tries to pull it from $session itself (which has its own problems)
  64. 64. Use exceptions instead of error flags 1 sub some_catalyst_action :Local { 2 my( $self , $c ) = @_; 3 4 try { 5 my $validated_params = validate_request_params( $c->request->params ) 6 7 my $step_one_result = $c->model(BusinessLogic)->do_step_one( $session , $validated_params ); 8 9 my $step_two_result = $c->model(BusinessLogic)->do_step_two( $step_one_result, $session );10 }11 catch { $c->detach( error , [ $_ ] ) };1213 $c->stash({14 one => $step_one_result ,15 two => $step_two_result ,16 });17 }
  65. 65. Throw exceptions as early as possible
  66. 66. Throw exceptions as early as possible• If you’re going to throw an exception because you didn’t get passed something, do it ASAP.
  67. 67. Throw exceptions as early as possible• If you’re going to throw an exception because you didn’t get passed something, do it ASAP.• In general, the quicker you can die(), the better – because it reduces the amount of code that might contain the bug.
  68. 68. Don’t catch exceptionsexcept to handle them
  69. 69. Don’t catch exceptions except to handle them• Most of the time, your business logic code is going to throw exceptions, not catch them
  70. 70. Don’t catch exceptions except to handle them• Most of the time, your business logic code is going to throw exceptions, not catch them• If you do catch an exception, you should be trying to fix the problem.
  71. 71. Don’t catch exceptions except to handle them• Most of the time, your business logic code is going to throw exceptions, not catch them• If you do catch an exception, you should be trying to fix the problem.• Don’t catch exceptions just to munge them or log them and re-throw them. Munge them or log them before you throw them.
  72. 72. Web application error actions should handle exceptions 1 sub error :Private { 2 my( $self , $c , $error ) = @_; 3 4 my $message = An unexpected error happened.; 5 6 # NOTE: duck typing 7 $message = $error->user_visible_message 8 if( $error->has_user_visible_message and ! $error->is_private ); 910 $c->stash({11 message => $message ,12 template => error,13 });14 }
  73. 73. Web application error actions should handle exceptions 1 sub error :Private { 2 my( $self , $c , $error ) = @_; 3 4 my $message = An unexpected error happened.; 5 6 # NOTE: duck typing 7 $message = $error->user_visible_message 8 if( $error->has_user_visible_message and ! $error->is_private ); 910 $c->stash({11 message => $message ,12 template => error,13 });14 } (again, not the best example ever...)
  74. 74. Further reading• Throwable::X: common behavior for thrown exceptions (<http://rjbs.manxome.org/rubric/entry/1860>)• Exceptionally Extensible Exceptions (<http://advent.rjbs.manxome.org/2010/2010-12-03.html>)• Structured Data and Knowing versus Guessing (<http://www.modernperlbooks.com/mt/2010/10/structured-data-and-knowing-versus-guessing.html>)
  75. 75. Thanks for your time this evening!
  76. 76. Questions?

×