13.14. Unpacking Exceptions
If an exception handler becomes long or complex, you may need to refactor parts of it. For example, consider the X::EOF handler inside TRy_next_line( ) from the "OO Exceptions" guideline: sub try_next_line { This code would seem to be cleaner and easier to extend if the separate rethrows were refactored like this: sub try_next_line { # Give get_next_line( ) two chances... for my $already_retried (0..1) { # Return immediately on success, but catch any failure... eval { return get_next_line( ) }; # If we can handle this exception... if (X::EOF->caught( ) ) { # Fail on irremedially bad cases... fail_if_incorrigible($EVAL_ERROR, $already_retried); # Otherwise, try rewinding the filehandle... seek $EVAL_ERROR->handle( ), 0, 0; } # Otherwise, let some caller handle it... else { $EVAL_ERROR->rethrow( ); } } } Refactoring in this way is usually a highly recommended practice, but in this case it has the potential to introduce a subtle bug. The problem is that the $EVAL_ERROR exception variable (a.k.a. $@) is a global variable. So if fail_if_incorrigible( ) happens to throwand then internally catchsome other exception during its execution, that nested exception will overwrite $EVAL_ERROR. So, after the call to fail_if_incorrigible( ), the exception variable might still have the original X::EOF exception, or it might contain some other totally unrelated exception, left over from the internal machinations of fail_if_incorrigible( ). That uncertainty makes the seek statement after the call to fail_if_incorrigible( ) problematic. It tries to seek the handle that was sent back inside the original exception, but there's now no guarantee that $EVAL_ERROR still contains that exception. Fortunately, it's comparatively simple to solve this problem: just copy the exception into a lexical variable before attempting to handle it: if (X::EOF->caught( ) ) { my $exception = $EVAL_ERROR; Using the Exception::Class module makes this practice even easier to follow, because its version of the caught( ) method always returns a copy of $EVAL_ERROR on success. So you can just write: if (my $exception = X::EOF->caught( ) ) { |