hsmyers has asked for the wisdom of the Perl Monks concerning the following question:
Using perlcritic -4 on my code I'm down to messages like:
Found use of die. Use an exception instead at line 38, column 8. Exce
+ption objects
should be used instead of the standard Perl error mechanism. (Severit
+y: 4)
The 'offending' code is:
$hECO = retrieve($ECO_path)
or die "Couldn't open $ECO_path : $!";
$hNIC = retrieve($NIC_path)
or die "Couldn't open $NIC_path : $!";
$hOpening = retrieve($Opening_path)
or die "Couldn't open $Opening_path : $!";
I'm wondering if the following is equivalent?
eval {
$hECO = retrieve($ECO_path);
1;
} or do {
print "Couldn't open $ECO_path : $@";
exit;
}
eval {
$hNIC = retrieve($NIC_path);
1;
} or do {
print "Couldn't open $NIC_path : $@";
exit;
}
eval {
$hOpening = retrieve($Opening_path);
1;
} or do {
print "Couldn't open $Opening_path : $@";
exit;
}
I'm also wondering if it is safe? What alternatives are there that don't drag in a mountain of dependencies? I've looked at Try::Tiny and TryCatch. Error is pretty much deprecated. If I have to I'll leave the code as is— but as a sort of intellectual challenge, I'd like to see what can be done.
--hsm
"Never try to teach a pig to sing...it wastes your time and it annoys the pig."
Re: eval to replace die? (Exceptions and Error Handling References)
by eyepopslikeamosquito (Archbishop) on Oct 04, 2010 at 03:36 UTC
|
I doubt that PBP (2005) is up to date with the latest developments in this area.
The recently released (2010) 2nd edition of
Effective Perl Programming by brian_d_foy recommends the CPAN
Try::Tiny module (released 2009)
and the new core autodie pragma (added to perl 5.10.1 in 2009): see items 101 ("Use die to generate exceptions") and 103 ("Handle exceptions properly").
In particular, item 103 summarizes with "handling $@ is tricky, but Try::Tiny does it correctly".
Note that Try::Tiny has zero dependencies.
2015 update: The excellent Modern Perl book by chromatic
also recommends Try::Tiny; from its "Style and Efficacy" chapter, "Exception Caveats" sub-section:
Using $@ correctly requires you to navigate several subtle risks:
- Unlocalized uses further down the dynamic scope may modify $@
- It may contain an object which overrides its boolean value to return false
- A signal handler (especially the DIE signal handler) may change $@
- The destruction of an object during scope exit may call eval and change $@
use feature 'try' (2023 update: for perl 5.38+)
As clarified with a complete perl v5.38 example program,
note that perl v5.34 added try/catch syntax, inspired by Syntax::Keyword::Try:
use feature 'try';
try {
do_a_thing();
}
catch ( $e ) {
warn "It failed - $e";
}
perl v5.36 added finally blocks to try/catch, also inspired by Syntax::Keyword::Try:
use feature 'try';
try {
do_a_thing();
}
catch( $e ) { ... }
finally {
cleanup();
}
Note that try-catch feature is no longer experimental with perl v5.40 - its use without a finally block no longer prints a warning but it still must be enabled with the 'try' feature.
The optional finally block is still considered experimental and emits a warning, except when explicitly disabled with no warnings "experimental::try";.
Some perldoc References
- die - is how you throw exceptions in Perl
- eval - used to execute a little Perl program, trapping any errors encountered so they don't crash the calling program
- Carp - alternative warn and die for modules
- autodie - Replace functions with ones that succeed or die with lexical scope
- Fatal - Replace functions with equivalents which succeed or die
Exception Safety and RAII
RAII (Resource Acquisition is Initialization)
is very useful when throwing exceptions in Perl (via die).
To give a very simple example, this code:
use strict;
use warnings;
sub fred {
my $fname = 'f.tmp';
open( FH, '<', $fname ) or die "error: open '$fname': $!";
print "file '$fname' opened ok\n";
# ... process file here
die "oops"; # if something went wrong
close(FH);
}
my $ok = eval { fred(); 1 }; # see [id://11130946]
if (!$ok) { print "died: $@\n" }
# oops, handle FH is still open if an exception was thrown.
my $line = <FH>;
print "oops, FH is still open:$line\n";
is not exception-safe because the global file handle FH is not closed when die is called.
A simple remedy is to replace the ugly global FH with a lexical file handle $fh,
which is auto-closed at end of scope (RAII):
use strict;
use warnings;
sub fred {
my $fname = 'f.tmp';
open( my $fh, '<', $fname ) or die "error: open '$fname': $!";
print "file '$fname' opened ok\n";
# ... process file here
die "oops"; # if something went wrong
close($fh);
}
my $ok = eval { fred(); 1 }; # see [id://11130946]
if (!$ok) { print "died: $@\n" }
print "ok, \$fh is auto-closed when sub fred exits (normally or via di
+e)\n";
References Added Later
Some useful recent Perl Monks nodes:
Some general advice from On Coding Standards and Code Reviews (API Design Checklist section):
- Error handling. Document all errors in the user's dialect. Prefer throwing exceptions to returning special values. Prefer to find errors at compile time rather than run time.
As noted at On Interfaces and APIs:
- Perl 5's "string eval" and "block eval" is an example of violation of the "principle of distinction", aka "different things should look different"
Some older nodes:
Some external references:
CPAN Modules:
- Syntax::Keyword::Try by Paul Evans - a try/catch/finally syntax for perl
- Syntax::Keyword::Defer by Paul Evans - execute code when leaving a block (via a defer block)
- Feature::Compat::Try by Paul Evans - make try/catch/finally syntax available. On older versions of perl before such syntax is available, it is provided instead using the Syntax::Keyword::Try module.
- Feature::Compat::Defer by Paul Evans - make defer syntax available. On older versions of perl before such syntax is available, it is provided instead using the Syntax::Keyword::Defer module.
Wikipedia:
ISO C++:
- Exceptions and Error Handling (As a rule of thumb, exception handling is extremely cheap when you don't throw an exception. It costs nothing on some implementations. All the cost is incurred when you throw an exception: that is, "normal code" is faster than code using error-return codes and tests. You incur cost only when you have an error)
SO:
See Also
Updated: added use feature 'try' and Exception Safety and RAII sections.
Updated sample code based on Re: Bug in eval in pre-5.28.
| [reply] [d/l] [select] |
|
I like the zero dependencies part. While poking about, I installed TryCatch and wasn't pleased with the more than 400 files that tagged along. Don't think I want to do that to folks using my module. No way to avoid some build up, but minimizing it is bound to be a good thing (™) I also read EPP 2nd and noticed the reference to use autodie; and the other modules— maybe this is the way to go. At least the Try::Tiny option; I'd just as soon avoid dragging anyone into using a more modern Perl unless I have to!
--hsm
"Never try to teach a pig to sing...it wastes your time and it annoys the pig."
| [reply] [d/l] |
Re: eval to replace die?
by BrowserUk (Patriarch) on Oct 04, 2010 at 03:06 UTC
|
The clue to the appropriate action is in the error message:
Exception objects should be used instead of the standard Perl error mechanism.
The action is: Question.
And when no good answers are forthcoming--and they won't be--consign this "post-modern", revisionist rubbish to the bit bucket and get on with life!
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] |
|
... consign this "post-modern", revisionist rubbish to the bit bucket and get on with life!
Perhaps you'd be more comfortable with Tcl circa 1998.
Remember, everyone else: you don't need reliable error-handling code if you never make errors.
One big drawback of string exceptions is that you have to parse strings to decide how to handle them. Parsing strings (especially to determine flow control) is fragile.
This fragility is a small risk in smaller applications, but it's worth considering alternatives in larger applications where you must handle exceptions.
| [reply] |
|
The source of errors isn't always within the script - the "open or die" idiom is used extensively in the documentation, the Camel book etc - for quick scripts, to say, parse a system file and print some useful info, this construct is perfectly suitable - exception handling etc has its place, agreed - but it's a judgement call where that place is.
| [reply] |
|
|
|
|
|
Perhaps you'd be more comfortable with Tcl circa 1998.
No. I'm quite happy with Perl thank you.
You're the one who seems to want to turn Perl into Java.
One big drawback of string exceptions is that you have to parse strings to decide how to handle them. Parsing strings (especially to determine flow control) is fragile.
This is typical of your response to relatively simple problems. Throw yet another layer (or three) of complicated code on top, in an attempt to make the problems "go away".
Yes, error messages change occasionally.
Not often, but the recent addition to "Uninitialised value" is a ready exceptional hook upon which to hang your demagoguery. Despite that no one in their right mind is going to use exception handling to deal with an uninitialised variable(*). C'st la vie.
(*) Perhaps you'd offer us a worked example of how using Exception::Class to trap an uninitialised variable would have 'saved the day', when the variable name was added to the message?
We could then examine the basis of your justifictions; assess the impacts, pro's & con's of your suggested alternative; explore alternative alternatives.
Of course, you won't. Because you're a busy man with no time to explain your "wisdom", much less open it up to the challenge of open debate.
If you're performing an exact match, a substring match, or a regular expression, any change to the text of that message in a subsequent release of Perl 5 could change the way your code behaves.
So, the crux of this is that instead of the simplicity and clarity (both code and error reporting) of:
#! perl -slw
use strict;
sub divide {
my( $e, $d ) = @_;
return $e / $d;
}
my $result = eval{ divide( @ARGV ) } or die $@;
print "Result: $result";
you're advising people to use (something like*):
(*)This doesn't actually work. Because I can't work out how to use the module. Because there are no simple, complete, working examples that I can find. Probably because it is so insanely complicated to use that simple, complete, working examples aren't possible!
#MyExceptions.pm
package MyExceptions;
use Exception::Class (
'MyException',
'AnotherException' => {
isa => 'MyException'
},
'YetAnotherException' => {
isa => 'AnotherException',
description => 'These exceptions are related to IPC'
},
'ExceptionWithFields' => {
isa => 'YetAnotherException',
fields => [ 'grandiosity', 'quixotic' ],
alias => 'throw_fields',
},
);
1;
## Exceptions.pl
#! perl -slw
use strict;
use MyExceptions;
sub divide {
my( $e, $d ) = @_;
MyExceptions->throw( error => 'Divisor undefined' ) unless defined
+ $d;
MyExceptions->throw( error => 'Divisor is zero' ) if $d == 0;
return $e / $d;
}
# try
my $result = eval { divide( @ARGV ) };
my $e;
# catch
if ( $e = Exception::Class->caught('MyException') ) {
die 'You must supply two arguments' if $e->error eq 'Divisor undef
+ined';
die 'The second argument must not be zero' if $e->error eq 'Diviso
+r is zero';
die sprintf "Unanticipated exception: '%s' at \n%s\n", $e->error,
+$e
}
else {
$e = Exception::Class->caught();
ref $e ? $e->rethrow : die $e;
}
If you are unable--or more likely, unwilling--to see all the ways that can go wrong. That this is even more dependant upon string comparisons. And all the addition problems you've created for the maintenance programmers to have to unwind, sift through and reverse engineer through all those extra layers, when it does go wrong. Then I guess they'll just end up reaping what you sow.
Even if you avoid comparing $e->error() to decide how to deal with the exceptions,
you are still relying upon MyException->caught( 'MyException' ),
which replies upon Exception::Class::Base->isa( 'MyException' ).
And what does isa() do that isn't subject to exactly the same error vectors a string compare?
There are times when exceptions are useful. But reaching for the insane complexity of modules like Exception::Class just in order to achieve "Java-esque"-ness; rather than something perlish-ly simple like Try::Tiny:
is madness. Java-envy of the worst kind.
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |
|
|
|
|
Well that is the alternative I mentioned towards the end of my rambling, but I really am curious as to any answers that might be out there. I realize that there are a lot of things in PBP that are questionable, same could be said of PerlTidy for that matter ;-)
--hsm
"Never try to teach a pig to sing...it wastes your time and it annoys the pig."
| [reply] |
Re: eval to replace die?
by JavaFan (Canon) on Oct 04, 2010 at 09:05 UTC
|
I'm wondering if the following is equivalent?
No, they aren't equivalent. If retrieve dies, then in the first snippet, nothing will happen, as the program has terminated before the reaching the die. In the second snippet, it will print the messages, as the eval captures the die. OTOH, if retrieve doesn't die, but returns a false value, the first snippet will die, showing the message, where as the second snippet, the eval blocks return true, and neither the prints, nor the exits will occur.
Evals do not replace dies. In fact, you would use eval in combination with a die. In fact:
sub sub_that_may_die() {
die "Oops!" if something_I_do_not_like_happens;
}
eval {sub_that_may_die()}
is an exception mechanism. die isn't program termination, it is throwing an exception. And eval captures such exceptions. It's only exceptions that aren't captured by an eval that lead to program termination. | [reply] [d/l] [select] |
|
Ignoring the tennis match going on here, your answer of 'not equivalent' raises the obvious question of what is? I will probably upload to CPAN with the problem resolved using Try::Tiny, but I still wonder if there is a relatively simple Perl solution that just hasn't occurred to me?
--hsm
"Never try to teach a pig to sing...it wastes your time and it annoys the pig."
| [reply] |
|
If the problem is that exception objects should be used instead of die(), then Try::Tiny won't solve this at all!
Try::Tiny provides an easy-to-use alternative to eval - but without using some other module to provide exception objects, you'll still be stuck having to call die() in the catch{} block.
| [reply] |
|
|