
|
Show me the exceptions
Joel has certainly started a lively debate on the subject of exceptions. I've had feelings somewhat in common with Joel's for some time, but I'm willing to be convinced otherwise. To do that, show me a non-trivial example of the use of exceptions (preferably in C++) for something other than exiting the program. Note that I have a shelf full of trivial examples, so those need not apply. (I also have ~100Kloc worth of bad examples, so those need not apply either). I'd prefer if the example were multi-threaded, because I'd love to see how folks deal in a multi-threaded environment, but I'll take anything non-trivial.
I have the suspicion that in order to make exceptions work well, the program must be structured in a certain way - I just don't know what that way is.
Show me an example of something where exceptions work well, and perhaps I'll start believing they are something more than a nuisance.
Michael Kohne
Tuesday, October 14, 2003
Just a small side question, but do you feel the same about transactions in Databases?
Just me (Sir to you)
Tuesday, October 14, 2003
James Robertson has a quite interesting example in this blog entry:
http://www.cincomsmalltalk.com/blog/blogView?showComments=true&entry=3243543356
It is not C++, though.
Giovanni Corriga
Tuesday, October 14, 2003
Kind of bogus to compare transactions and exceptions. A transaction is merely a special way of handling errors, however that error might be raised. I don't think T-Sql has exceptions: it has a global error code that automatically halts the task if it gets above some value.
I've never been quite satisfied by exceptions, either. You never seem to have quite enough context. The thrower never seems to provide quite enough information, the catcher only roughly knows what was going on, and no one in the middle ever provides any additional information. Throwing an exception always seems so destructive, like blowing a land mine up in your code. To convert code that once threw an exception, to instead handle the error and resume would often require some heroic coding.
I think the next error handling mechanism I want to experiment with is some sort of error/progress log object that can be checked at any time to see what if anything has gone wrong. It is inspired by the idea that both error handling and progress bar handling involve some of the same concepts: having some idea of context as to what is going on and where we are in the game plan. And there never anything so useful as a log. Unfortunately, I haven't gotten the chance to put the idea to code yet, so I don't know how well it will work.
Keith Wright
Tuesday, October 14, 2003
"To do that, show me a non-trivial example of the use of exceptions (preferably in C++) for something other than exiting the program" - Michael Kohne
Ok, I'll bite, but I'm not sure exactly what you're looking for; the 'other than exiting the program' bit throws me, exceptions are for exceptional situations they're not a control flow primitive, but anyway...
Here's a link to some reasonably non-trivial, Win32, multi-threaded, socket server code. (no yawning at the back ;) )
http://www.lenholgate.com/archives/zips/SocketServers.zip
The code is briefly discussed here:
http://www.lenholgate.com/archives/000082.html
and that points to more detailed, but slightly out of date articles that talk about the code in more depth.
The code wraps its access to Win32 primitives in classes that throw exceptions when 'things that shouldn't happen' happen. Two that you might want to look at are the CEvent class that wraps events and the CIOCompletionPort class that wraps IOCPs. These classes are then used throughout the code. We don’t need to check for the failure situations that may occur but most probably wont. We don’t need to propagate errors when these things occur. We use RAII so we are sure that things get cleaned up when the owning class goes out of scope. Other places in the code use exceptions for enforcing invariants (CNamedIndex::Add(), CIOBuffer::Allocator::Release() and CSocketServer::Socket::Attach() - I agree that an assertion could achieve the same thing but, as you can see, I tend to favour exceptions over assertions... In some situations; CNamedIndex::Add() springs to mind, a redesign of the code could achieve the same thing by making the situation that the exception is reporting impossible to achieve.)
The only exception handlers are in main, at the outermost level of thread functions and in callback methods in client code to prevent the client throwing exceptions into the framework code. In effect the exceptions will always propagate out to the logical boundary between the code we own and the code we don’t...
There are a couple of functions that take optional bools which change them from returning false on failure to throwing an exception on failure. They're a convenience for client code and allow flexibility (not 100% convinced I like it, but I haven't been a client of the code recently enough to remember how useful it is).
Looking at the simplest server in the zip (EchoServer) there are 70 throws and 9 catches in the server and library.
What they give me 'other than exiting the program' is the knowledge that I wont forget to look for, handle and propagate errors that I'd probably otherwise convince myself 'could never happen' and ignore. Because of this the code is, IMHO, cleaner than it would otherwise be.
I don't like the catch(...) handlers as they're a bit too keen in Win32-land and I should really be dealing with the SEH stuff in a better way but... It's on the list of things to do and they've only given me problems once.
Len Holgate (www.lenholgate.com)
Tuesday, October 14, 2003
I think I understand what problem the errcode people have. (I'm sort of the opposite, someone who only knows exceptions and is very curious about the mean old days.)
* You often have intermediate functions operating on an api. You want these things to be reusable (like map in python/lisp/haskell), which they would often not be if they knew anything about your errhandling scheme. Errorhandling is usually specific, which contradicts writing general code.
* Oftentimes, different levels of the program are varyingly good at handling different errors. For example, the code 2 levels up from the call knows about the GUI system and can handle a file missing error (do it earlier, and you dirty up your abstraction). Code 5 levels up knows how to deal with an out of memory error.
* Writing code quickly means you should defer exceptions until it's conceptually right to do so. Exceptions are just that -- exceptional situations. These are different abstractions from what you normally want the code to do.
* The problem with exceptions is that it busts out of functions, losing a lot of context. There used to be another kind, which resumed execution to the point where the exception was thrown. However, in Stroustrup's _Design and Evolution of C++_, he explained that no one used it! All the arguments made it seem just as important as the kind we all use now, but when he actually looked for examples in the wild, people just were happier with the other kind.
I don't think people who write errcodes are bad at all; there are some documentation benefits when all the errhandling is in one place. The proof is in the bugfree code. But I imagine people good at errcodes craft Byzantine armor, while exception code makes for more looser and flexible stuff. Errcodes seem more mentally punishing.
Tayssir John Gabbour
Tuesday, October 14, 2003
Although I see the argument that exceptions are implicit error handling mechanisms (and Joel seems to like extremely explicit code paths), I recommend you use a platform that provides GOOD exception handling and a sensible stack trace and I think you will no longer want to go back.
When something goes wrong in my .NET code, I get a stack trace with line numbers where the error occurred. (this is extremely useful when our testers are trying the software - they can send us the trace using the error reporting tool) If there are nested exceptions, I can see the inner exceptions and their stack traces.
To get the same amount of information with explicit handling requires writing exceedingly precise error-checking/reporting code, which gets tedious if you have to add it after EVERY method call in your code. (you either have to allocate unique error numbers or start reporting the method name, the class name, the file name, line number, maybe, etc.) The runtime already knows what methods I am currently executing and where they are at, why should I have to re-type of all that in? This introduces the potential for error and maybe more bugs!
And also, the finally block makes clean-up more clear because it is not sprinkled throughout the code. It means I don't have to write an fclose() in each error handling block, just one at the end, which first checks if the file pointer is that of an open file.
Lastly, you can set VS.NET to stop execution whenever a custom subset of exceptions is raised, similar to how it allowed it with C++. This means if anything goes wrong, I can follow with my debugger how the error is reported. If you have to test the return value of everything, you probably have to put breakpoints in all those error handling blocks.
This is an issue I would personally like to debate with Joel himself. :)
Oli
Tuesday, October 14, 2003
Recent Topics
Fog Creek Home
|