Fog Creek Software
Discussion Board




Reserve exceptions for exceptional stuff

I throw quite some exceptions but I rarely catch them.
In my book exceptions should only be used for real error conditions, and in real error conditions I want a program abort with good debug information.

As someone put it, exceptions should not be used to communicate states or expectable problems in a program, they should only be used for unexpected things. In other words, like asserts.

This way exceptions work well for me.

Sam

Sam
Tuesday, October 14, 2003

Unfortunately, it doesn't work that way.

What's expected by you is not expected by someone else, and vice versa.

Trying to open a non-existent file for reading.  Should that raise an exception? If the program relies on that file, yes. If the program can do without it, no. As the author of the "open_file" function, would you throw an exception?

About 50% of my "open file" calls must succeed, and for the other 50%, it is perfectly non exceptional if they fail. The right thing to do, in my opinion, would be to have a "must_succeed" variant of the call (through another parameter or a different function call altogether). But I can only make that assertion because I'm the _user_ of the library.

If I had a 99% to 1% ratio, I'd opt for throwing exceptions; If I had a 1% to 99% ratio, I'd opt for not throwing exceptions.

As a library/framework designer, there often isn't a right definition of what is exceptional and what isn't; And adding a 'must-succeed' parameter or variant to every supported function/method is IMHO, a horrible solution.

Personally, I'm in the return code camp; "errno" rules if you use it correctly. "errno" combined with a technique I call "failure state" for lack of a better name, allow you to write code which is, in the general case, more concise, simpler, faster yet more robust than exceptions. I'll post about it later.

Ori Berger
Tuesday, October 14, 2003

Ori,

I agree that an open_file function in a general purpose API probably shouldn't throw unless they provide an open_file_if_it_exists function or sufficient level of granularity within the API so that you can call a function to open a file that may exist or may not exist. But as the code becomes more application specific the classes that manage a file that MUST exist should throw if that file doesnt exist.

How you achieve this depends on the situation, sometimes I wrap an API that isn't object based in an object based manner and manage error codes from the underlying API and throw exceptions where appropriate... In general I aim to convert error returns into exceptions at the lowest level possible.

Personally, in this situation, I'd actually prefer an API that had two operations called, perhaps, OpenFile() and DoesFileExist() rather than just having a single Open() call that does multiple things or an OpenMayFail() and an OpenMustSucceed()... If the API that I need to work with doesn't provide these primitives then it's usually no trouble to provide a layer that does...

Like all tools in the toolbox exceptions aren't a silver bullet and can be misused; but I find they work for me. I end up with less code and the code that's there seems to be more robust and easier to change.

Len Holgate (www.lenholgate.com)
Tuesday, October 14, 2003

Of course if I have a framework throwing an exception for a missing file but consider the fact that the file is missing a 'state' and not an error I'd catch the exception and translate it into some kind of state.

So I dont really think it's important for me if the framework thinks something is an error or not, I'll reinterpret the result to my liking even in cases where it might be an exception.

Sam
Tuesday, October 14, 2003


This whole debate about exceptions hasn't touched yet on ASSERTs, and I think that's the cause of some trouble. Several people have said, in this thead and the past couple, that exceptions should only be used for serious error conditions and when caught should exit with good debugging information.

This opinion is confused.  Any condition that should exit with good debugging information should be flagged with an ASSERT.  They're handy debugging tools for two reasons: they halt the code at the place where the error was first detected and they can be sprinkled liberally about your code without performance problems because they are removed automatically from release builds.

I've found exceptions quite difficult to use for debugging, on the other hand, and for the same reasons.  Unless the exception is caught immediately after it is thrown, the stack has unwound at least to some extent which means that you lose the opportunity to see the context for the error in the debugger.  Stack traces and whatnot could be generated and embedded into the exception, of course, but it's still not a perfect solution and you end up shipping release code with debug information embedded.  It's ugly and slow.

Exceptions should be used exclusively to handle error conditions that arise in a program that can be said to be free of bugs.  The network goes down during a file transfer; the user has deleted the splash animation file you're trying to open; his little brother thinks it's cool to enter his username for your app in all symbols, etc.  All these things are great candidates for generating exceptions.  The other interesting thing about limiting exceptions to handling only this subset of errors is that the handling code is rarely more than a couple of levels shallower than the generating code.  The big catchall statement around the main loop is no longer necessary.

Colin Fletcher
Tuesday, October 14, 2003

"Trying to open a non-existent file for reading.  Should that raise an exception?"

The answer to this question depends on the specification of the function.  If the function specifies that as a precondition to calling it, the caller must ensure that the file actually exists, then this is an exceptional situation - a precondition violation.  If the specification does not expose this assumption, then the function must succeed (and satisfy all of its post-conditions).

Absent the use of assertions, most exception-related material is confusing and arbitrary.  This is probably why some folks think that exceptions are ruinous to their code.

DBC
Tuesday, October 14, 2003

Len, and DBC: Do note that your approach, DoesFileExist() before Open, which could be implied in the contract, is a recipe for race conditions.

Because the file system is not controlled by the program, there is no way one can assure an OpenFile() call will succeed in advance! DoesFileExist() may return true, but the file may have been deleted a millisecond later, and the OpenFile() will fail. This is most definitely a _bug_, because if DoesFileExist() had happenned a millisecond later than it did, it would have noticed the file was gone and acted differently.

"open a file, but only if it exists" operation is one that _must_ be available atomically. Failure to properly support it leads to code with irreproducible bugs and often exploitable security holes.

Ori Berger
Tuesday, October 14, 2003

Hi Ori,

As usual, you are correct.  I don't think that I was advocating one approach to implementation over the other, though.  Just trying to observe that the answer to the question that was asked depended on the semantics/intent of the specified behavior.  I wasn't really talking at the level of code, more at the level of design.

Obviously, when it comes time to implement the specified behavior, you could run into issues like the situation you describe.  Proper attention up front to the specified behavior, though, would flag a problem here since it would be obvious that the specified behavior could not be implemented.  I prefer this approach to making decisions about semantics while I type :)

It is possible to build systems where this is not a problem, but the systems that most of use every day do not operate that way.  For an example, see the use and meaning of the "separate" keyword in Eiffel.

My apologies. I should have spoken more clearly.

DBC
Tuesday, October 14, 2003

Ori

I agree that it introduces a race condition and I wouldn't expect an OS operation to be structured as two calls but the requirements at an application level may be different. If we need to perform a 'create or open' on a file then that needs to work atomically, if we're just opening a file if it exists then that doesn't necessarilly need to be atomic.

Len Holgate
Wednesday, October 15, 2003

*  Recent Topics

*  Fog Creek Home