Re: Enforcing exception catching
by Ovid (Cardinal) on Feb 12, 2004 at 00:29 UTC
|
You might want to read what Bruce Eckel has to say about checked exceptions. The short version: checked exceptions sound better than they actually are.
In my experience, having a robust test suite is a great way to go. If a programmer misses something, write a test to verify the bug by watching the test fail, fix the bug, rerun the test and make sure it passes. Gradually over time, you can build a nice test suite which is going to cover the problems you face.
With checked exceptions, a programmer who's under the gun might stub out some exception handling code and then later forget to flesh it out. It's a natural problem, but checked exceptions can lull you into the false belief that you're really handling those issues.
For other readers unclear on this: think of a checked exception as something that can fail forcing you to wrap it in an eval at compile time and then testing the result:
eval {$some->method};
if ($@) {};
If you were forced to do that at compile time, you might see a lot of stubbed out error checking like I demonstrated. In Java, it's something like this:
try {
some.method();
}
catch (DealWithThisExceptionLater e) {}
Personally, I'd feel much more comfortable with a nice test suite that I can build on rather than littering the actual code with stuff that I might never need.
| [reply] [d/l] [select] |
|
Thanks, that's a good read. I realize that checked exceptions (I gather this is what they are called :)) are not a panacea, but it's better than nothing. Usually, this is how mistakes are made:
- programmer converts function ABC::xyz to use exceptions (throw them);
- then he modifies some function on a higher lever to handle the thrown exceptions;
- then he happily goes his way forgetting to check what else can call ABC::xyz
With this scenario, the checked exceptions would ID this problem on very first run.
| [reply] |
Re: Enforcing exception catching
by adrianh (Chancellor) on Feb 12, 2004 at 00:33 UTC
|
Surprisingly, following the latest OO conventions has led to serious problems and this question of mine
The niggler in me has to point out that the problem isn't really anything to do with exceptions - but with the change over. You would have the same problem in reverse if you were switching from exceptions to return values.
Is there a way to force calling functions to place calls to things that can throw exceptions in try{} block? (And I know the answer is probably "no"). Java has this type of enforcement built into the compiler.
If by "force" you mean give a compile-time error if you don't (as Java does for checked exceptions) then the answer is no. You can't get enough information at compile time because of Perl's dynamic nature.
Just as a point of information there are a fair number of people (myself included) who think Java's checked exceptions were an interesting but failed experiment. See Does Java need Checked Exceptions? and Checked Exceptions Are Of Dubious Value for some thoughts on this.
If there's no programmatic way of enforcing the practice, I'd like to hear how to cope with this besides preaching diligence to every member of the team -- people can still make mistakes.
During the changeover maybe some static code analysis would be a good idea. Grep through the sources for the method calls that can cause exceptions and do manual checks and code reviews.
In the longer term the best solution (and this applies to exceptions and return values) would be to improve your test suites so that it exercises the error handling code more. Use mock-objects to generate exceptions in situ and check that your application handles them appropriately.
| [reply] |
|
| [reply] |
Re: Enforcing exception catching
by hossman (Prior) on Feb 12, 2004 at 02:09 UTC
|
I agree with adrianh, your problem is not that the Exceptions aren't been caught -- the problem is that you changed the "API" of your code. You're clients had a contract with you, that in an error/exceptional condition, you would do "foo" (presuably return undef, or false, or something like that) and then all of hte sudden you changed the rules on them, and started throwing exceptions -- so your program dies, which is what it's supposed to do if the exception/error isn't caught.
What you can do, is take advantage of the caller function to determine if a try call is anywhere in the call stack at the moment you encounter an error, and if so then use throw, otherwise just return undef.
If you're paticularly crafty, you can make a subclass of [cpan://Error] that overrides the throw function to do all the work for you, and anytime it encounters an exception with no try in the stack trace, it can not only tell it's caller to return undef, it can log hte full strack trace with args to some special system wide log file which you can audit on a regular basis to track down code paths that don't include exception handling. once you're satisfied that you've done a full migration to the new world order, you can remove your overridden throw function (so that you have an empty subclass) and lose the slight performance penalty.
(This is something Iused to long for in java, a "warn" method that takes a "Throwable" and only throws it if someone upstream wants to catch it -- hence in situations where you are interested in knowing about every instance of MildlyAnoyingButNotCritialException, you can put a try block upstream that catches it, but in the cases where you really don't care you can trust the method you are calling to do the right thing and plod along past whatever situation would normally cause the MildlyAnoyingButNotCritialException)
Update:
- It just occured to me that [id://Abigail-II] and i suggested roughly the same thing, but with different implimentations.
-
While coming home from work, it occured to me that you don't have use caller to walk the stack, there's a simpler way to do it, that gives you finer control over detecting what exceptions people are catching, and which they aren't...
You can use Perl's dynamic scoping, to have a "global" hash keyed on exception types. Then in your new subclass of "Error" override the "try" method to local that hash, and set true values for any Exception types that you have catch clauses for. In your overriden "throw" method, you check that hash, and if the exception you want to throw "isa" any of hte keys in that hash, go ahead and throw it -- otherwise log that the exception would not be caught, so you are defaulting to the old behavior.
| [reply] [d/l] [select] |
|
| [reply] |
|
#!/usr/local/bin/perl
use strict;
use warnings 'all';
use vars qw(%catchable);
%catchable = ();
sub throw {
print "in $_[0], deciding if i should throw $_[1]: " .
(exists $catchable{$_[1]} ? "sure\n" : "nope, won't be caught\
+n");
}
sub foo {
# try { bar(); baz() } catch foo { ... }
local %catchable = %catchable;
$catchable{'foo'} = 1;
bar();
baz();
}
sub bar {
# try { yak(); } catch bar-1 { ... } catch bar-2 { ... }
local %catchable = %catchable;
$catchable{'bar-1'} = $catchable{'bar-2'} = 1;
yak();
}
sub baz {
# do some stufff...
throw('baz','foo');
throw('baz','uncaught-exception-1');
}
sub yak {
throw('yak','foo');
throw('yak','bar-1');
throw('yak','bar-2');
throw('yak','uncaught-exception-2');
}
foo();
__DATA__
bester:~> tmp/monk.pl
in yak, deciding if i should throw foo: sure
in yak, deciding if i should throw bar-1: sure
in yak, deciding if i should throw bar-2: sure
in yak, deciding if i should throw uncaught-exception-2: nope, won't b
+e caught
in baz, deciding if i should throw foo: sure
in baz, deciding if i should throw uncaught-exception-1: nope, won't b
+e caught
| [reply] [d/l] |
Re: Enforcing exception catching
by Abigail-II (Bishop) on Feb 12, 2004 at 00:00 UTC
|
Is there a way to force calling functions to place calls to things that can throw exceptions in try{} block?
Well, almost anything is possible in Perl. You could, after
compilation, put all functions in wrappers where the
wrapper walks the call stack and searches for a try.
If only some functions throw exceptions, you can give those
functions an attribute, and use the attribute mechanism to
place those functions in wrappers.
This is of course costly, as any function call is replaced
with two calls, with doing some work in between as well.
You can of course use a switch (command-line argument,
environment variable, etc) that recognizes a development
or testing
environment, and only places wrappers in development and
testing environments. This requires your test set to cover
all function calls (this is different from having all functions called!).
Abigail
| [reply] |
|
| [reply] |
Re: Enforcing exception catching
by arturo (Vicar) on Feb 12, 2004 at 13:41 UTC
|
Enforcing exception handling programmatically is only a part of the puzzle. The article by Bruce Eckel mentioned in this thread rests a good hunk of its case against checked exceptions on the claim that lots of programmers don't use them properly, which seems like an odd kind of criticism to make, IMO. Of course, he does make the valid point that, even if you implement checked exceptions in your framework, that you'd still need to make sure the team is dealing with them properly.
A better title for Eckel's article, I think, would have been Checked Exceptions are not a Magic Bullet, but I would have thought it obvious that there are no magic bullets. I think you're going to have to cope with this issue, as with nearly every issue that comes up in designing large systems, by "preaching diligence to every member of the team." And I'll parrot Ovid: make sure everybody runs tests. Anything else is the bad kind of lazy.
update : fixed a typo.
If not P, what? Q maybe? "Sidney Morgenbesser"
| [reply] |
|
Right, when java forces try/catch blocks around code, the tendancy is often for someone to throw the exception back up, and to write exception handling code around the whole program, which, in that case, is equivalent to a die(). Bad style, IMHO.
While exceptions are useful for error handling for basic java systems API's (Files, Sockets, etc) -- it's just as easy to let one crash the app as it is to avoid checking a return code in C. A developer has to be very diligant, and in all cases, smarter than the compiler. If you start to find exceptions annoying and don't give them respect, they will bite your head off and leave you with random errors that taunt your programs for years!
That referred to Java. I don't use C++ exceptions. My dialect of C++ is essentially "C with classes", and STL only when absolutely required. Exception handling in C can be very buggy, and when you mix C++ exception handling with Java JNI, very bad things can happen! (i.e. uncaught exceptions due to using different compilers between the JVM and C++ code). I'll stick to "C with classes" when I can, or better yet, just C.
| [reply] |
Re: Enforcing exception catching
by stvn (Monsignor) on Feb 12, 2004 at 14:46 UTC
|
First of all, you shouldn't need to wrap all calls to exception throwing functions in a try/catch block. Doing that is not much different that checking return error codes. You should catch exceptions where you can appropriately deal with them, not where they are being thrown. There is no use in catching an exception if you can't do anything useful with it. But to not catch an exception when you can deal with it (and possibly save the program from die-ing) is a design-time error not a compile/run-time error.
I agree too with adrianh, you probably need some static code analysis, and then have a talk about exceptions with all your developers to agree upon a method of usage. Consistent usage/paradigms is very important among groups of developers IMHO.
Secondly, If your program is throwing uncaught exceptions and die-ing, then wrap the main entry point in a try block. This will allow you to at least catch these exceptions and print out a stack trace before your program dies. Of course this may not be the best place to handle your exceptions, but at the very least you can do something rather than just letting things die.
Of course I am speak of Perl, not Java (which IMHO has got alot of language design problems). If you want a non-Perl reference to how to handle exceptions, don't look at Java instead look at a language like Ada. Java was made for web-applets, Ada was made by the U.S. Dept. of Defense for missle guidance systems (where reliability and smart error handling is key).
-stvn
| [reply] |
|
Secondly, If your program is throwing uncaught exceptions and die-ing, then wrap the main entry point in a try block. This will allow you to at least catch these exceptions and print out a stack trace before your program dies. Of course this may not be the best place to handle your exceptions, but at the very least you can do something rather than just letting things die.
The problem is that there are many programs using the libraries and I don't know which ones do not handle exceptions correctly. (Yet, I will be doing an audit). Placing all top-level calls in try{} blocks seems to be fixing symptoms, not the problem.
| [reply] |
|
Placing all top-level calls in try{} blocks seems to be fixing symptoms, not the problem.
It does only fix one symptom, but I never meant that to be a fix for the whole problem (sorry if i wasn't clear). IMO if you have code that throws an exception anywhere at all, your top level call should always have a catch block. You should never let an exception kill your program, you should always catch them so that you can clean up resources and such, or at a minimum log the exception and a stack trace.
Exceptions are a huge improvement over standard return value error checking not just because they allow you to centralize your error handling (sometimes much further up the call chain), but because they (the exception itself) can carry with it valuable information, like stack traces and contextual information from the call site. This kind of info is invaluable when trying to debug an error, and lightyears ahead of the old standard printf debugging technique.
-stvn
| [reply] [d/l] [select] |