There is No Try
This is a pretty common pattern of code:
In other words, try to call some method on
$self's helper object. If it fails, set an error and return
undef. Otherwise, return the result we got. You can find code like this all over the place, and unfortunately, it's got problems.
The most horrible, and possibly well known, is that
$@ being set isn't the right way to check for
eval failing. (Let's not worry about the pathological case of someone throwing a false exception object. That's just sick.)
The real reason is that until recently,
$@ could get easily clobbered by action at a distance. For example, look at the code again:
computation_helper which might look like this:
It sets up some helper object that we can throw away when we're done. We call its
compute_stuff method, which dies. At this point,
eval starts to return false, with
$@ set to that exception message. Unfortunately, little known to us, this code exists:
...and it's going to clobber our
$@ when the helper object gets destroyed – and that's going to happen once the
eval block is done and the helper object is no longer referenced by anything. Instead of testing for
$@, we should test for a false return from
This isn't any good, either, though. What if
compute_stuff can actually return false, or more specifically the empty string? We need to rewrite to force
Now we know that
eval will always return
1 unless it fails. This means we need to move the assignment to
$result inside the eval, and we need to move the declaration to an new, earlier statement.
Finally, to keep our eval from clobbering anybody else's
$@ in the future, we need to localize. In the end, our code ends up as:
Even if not a huge increase in the code needed for the operation, it's a bunch of magic to remember every time. If you don't do this sort of thing, though, you've got a big opening for horrible error-handling problems.
It's worth noting that these problems are greatly lessened in perl 5.14, where our hypothetical
DESTROY method would not have clobbered the outer
$@. If you can use 5.14, you should, and this is one very good reason.
That still leaves a bunch of boilerplate, though. This is why Try::Tiny has become so popular. Unlike many of the other, more feature-rich try/catch systems on the CPAN, Try::Tiny focuses on doing just the minimum needed to avoid the boilerplate above. For example, we'd write the code above as:
It encapsulates the localization of the error variable and the addition of the constant true value to check for. The
catch block is only entered if the
try block died, and the exception thrown is in
Try::Tiny also provides one more helper:
finally. It lets you provide a block (or many blocks) of code to be run after the
catch, no matter whether there was an error:
You can tell whether the
try block failed by whether there are any elements in
@_. Even if somehow the contents of
$@ couldn't be preserved, you'll find an undef in the argument list if the try failed. Otherwise, it will be empty. You can supply as many try blocks as you want.
Try::Tiny doesn't do much, but it nicely packages up a pattern that's important to use and really boring to type out every time. Not only does it save you from having to remember the details each time, but it gives you an interface that's easier to write and skim, too. Use it!