Exception handling (job stuff, rant)
by at 4:23 PM
The right way to handle an exception: Print out a debug message which allows the programmer to figure out where, and what, went wrong. Also, if it's not a critical error, try to put things in a consistent state.
The wrong way to handle an exception:
try {
On a related note, I'm currently having trouble debugging a bunch of code which was haphazardly ported from BREW to Java by a certain someone who basically just copy-pasted the C++ code over to a Java class and fixed the syntax until it compiled. Meaning, any time the compiler complained about an uncaught exception, he just wrapped it in a bogus /* some code which needs to work right or else you just get a bunch of NullPointerExceptions on every frame */} catch (Exception e) {}
try/catch like above. No debug message. Hell, it'd have been better if he'd just set the whole method throws Exception so that at least the default handler would have caught it and printed the stack trace.The way it is, though, a bunch of exceptions cause certain instantiations to fail, cascading to the point that the object which is expected to be called as a frame-render delegate is just null. With absolutely no hint as to where the initial failure occurred.
Hooray.
Comments
It's way too long ago for me to trust my memory, but I recall some of that being in Sun's own java.* implementations.
These days, in all projects I work on I have a permanent breakpoint set at -[NSException raise]. It's been oh so helpful.
throw, and unfortunately the MIDP runtime has its own top-levelcatch (Exception e) { System.out.println(e.toString); e.printStackDump(); }which prevents JBuilder from ever even seeing the exception. Though at least it prints out the stack dump so I can go to the appropriate method and set a breakpoint on the appropriatethrows.Throwable.Throwable()for some reason.public method throws Exception{
//do stuff as usual
//...
if SomethingBadHappens{
throw new Exception("Helpful message about how you screwed up the system")
}
Maybe Eclipse handles these things a little better, but that's besides the point...
http://weblogs.asp.net/oldnewthing/archive/2004/04/22/118161.aspx
http://weblogs.asp.net/oldnewthing/archive/2005/01/14/352949.aspx
I think the problem with the whole idea of exceptions is that it's an attempt at "something for nothing": Robust error handling without actually putting in error handling code.
News flash: Error handling still has to go somewhere. You still have to think about every failure case. Unless you just don't care, in which case your manual-error-based code will be pretty short and "elegant" too!
Exceptions are stupid.
bool LoadFileResource(std::string const& name) : Throws fileerror
{
FileResource a_file(name);
if(a_file.IsSuccessful())
{
// do stuff here...
return true;
}
throw fileerror;
return false;
};
So, y'see... [takes a long drag from a joint] (As I do all too often when looking at this code) In the Java version, "FileResource" throws the appropriate exceptions. But the guy who ported it to C++ stripped the exceptions. Rather than "fixing" the port of "FileResource", the guy who ported this code just wrapped the code to maintain the behavior. Okay, I could sort of deal with that ... But that's not the best part! The best part was that even though he had wrapped this call in a try/catch block... HE WAS ALSO CHECKING THE RETURN CODE!
Raymond's point is that with error-return-value based code, you "only" have to check the returned error values. I put "only" in quotes because, in real app level code, nearly everything you do has a return value. If, as he claims, in an exception-based model anything can raise an exception, then in the equivalent return-value-based model, anything can return an error code. The number of error cases to deal with is the same.
Moreover, it's not harder to ignore return values than it is to ignore exceptions. It's just as easy. The compiler doesn't help you unless you turn on really draconian warnings about ignored non-void return values (which in my experience no one ever does because it produces too much noise.)
The cost of return-value-based APIs is that the return value totally fuck up the API design. You lose the natural purpose of the return value, namely to return a result. This means you can no longer compose calls together in a single expression, like "firstThing().property().biggestFoo().increment()". Instead you have to put each of those calls on a separate line, with local variables to capture each intermediate result. Instead, the real function results turn into "out" parameters, which are annoying to work with, prone to errors, and very difficult in some language like Java.
Plus, you have to follow every call with an if( ) statement to test the return value. This results in really ugly code. There are sets of macros to make this a bit less painful, involving goto's and such. Those end up being a sort of pidgin version of real exception handling.
I spent a long time in a return-value-based world (the classic Mac Toolbox) and also a long time in an exception-based world (OpenDoc and Java). I prefer exceptions. Both can let you write bad code that doesn't handle errors, but with exceptions your overall code is much, much cleaner and less prone to all sorts of other issues.
NULLcan only mean so many things) without doing all sorts of call-by-reference stuff, which some see as impure.Unfortunately, C++ exceptions are useless because they're not implemented consistently, and Java basically rewards programmers for doing lazy "ignore it and it will go away" error handling (like in this entry).
If Java implicitly just threw unhandled exceptions rather than forcing the programmer to handle it explicitly, this issue wouldn't happen, because lazy programmers could just pretend exceptions don't happen and the people who have to clean up after them can just go, "Oh, hey, an uncaught exception happened here."
Better yet, if Java's
Throwable.Throwable()base constructor could be set to callprintCallStack()(which would have been trivial for Sun to put in as, say, a static configuration variable) or if there were a defaultcatch()block for uncaught exceptions or whatever, then lazy programming would still be okay, and there wouldn't be a need for added overhead by keeping track of the exception stack all the time.Granted, the examples in the first article you linked to are also valid criticisms of exceptions, but the situations described seem to also involve a pretty crappy application architecture to begin with. (Not that the architecture we're dealing with right now is particularly good, of course. But hey, all of that code is error-code-based; the exceptions are all from the JRE.)
The second article basically says, "Yes, exceptions can be used well, but only if you do them right." Then he goes on to say that you have to check every line of code - well, that's not quite true, you only have to check lines of code which can generate exceptions, and if code can generate an exception, then if it were to make error returns then you'd just have to check the error returns instead.
Exceptions at least let you batch up a bunch of error returns into a single
catchblock.Also, his point about seeing whether error-code-based code is bad or not doesn't really mean much — there's plenty of bad code like that in BREW Sprung and it's easy to recognize it as such, but that still doesn't make debugging it any easier, especially since (at least in my experience) you only come across that kind of code when you're trying to debug other code and trying to figure out where and why things are breaking. In many cases, Sprung error debugging led me on a wild goose chase.
Tracking down ignored exceptions in exception-based code could be way easier than tracking down ignored error returns in error-return-based code; the problem is that Java makes it easier to simply discard (as opposed to ignore) unhandled exceptions.
His final example showing how much harder it is to tell if exception-based code is good or bad doesn't make any sense to me, but that's probably because it's very specific to a single implementation of a single object in C#, and it doesn't actually do anything to handle exceptions anyway, so I don't see how it applies to exceptions as a whole.
My own favorite exception WTF:
{
hr = WinApiCall();
if( FAILED(hr) )
throw(HR);
// repeat ten times
}
catch(hr)
{
return hr;
}
This is especially annoying in Visual Studio, as it normally displays exceptions in the debug output window even if they are caught.
Visual Studio is nice in this respect...you can have it break on exceptions even if they are caught.