How did MS-DOS report error codes?


The old MS-DOS function calls (ah, int 21h), typically indicated error by returning with carry set and putting the error code in the AX register. These error codes will look awfully familiar today: They are the same error codes that Windows uses. All the small-valued error codes like ERROR_FILE_NOT_FOUND go back to MS-DOS (and possibly even further back).

Error code numbers are a major compatibility problem, because you cannot easily add new error code numbers without breaking existing programs. For example, it became well-known that "The only errors that can be returned from a failed call to OpenFile are 3 (path not found), 4 (too many open files), and 5 (access denied)." If MS-DOS ever returned an error code not on that list, programs would crash because they used the error number as an index into a function table without doing a range check first. Returning a new error like 32 (sharing violation) meant that the programs would jump to a random address and die.

More about error number compatibility next time.

When it became necessary to add new error codes, compatibility demanded that the error codes returned by the functions not change. Therefore, if a new type of error occurred (for example, a sharing violation), one of the previous "well-known" error codes was selected that had the most similar meaning and that was returned as the error code. (For "sharing violation", the best match is probably "access denied".) Programs which were "in the know" could call a new function called "get extended error" which returned one of the newfangled error codes (in this case, 32 for sharing violation).

The "get extended error" function returned other pieces of information. It gave you an "error class" which gave you a vague idea of what type of problem it is (out of resources? physical media failure? system configuration error?), an "error locus" which told you what type of device caused the problem (floppy? serial? memory?), and what I found to be the most interesting meta-information, the "suggested action". Suggested actions were things like "pause, then retry" (for temporary conditions), "ask user to re-enter input" (for example, file not found), or even "ask user for remedial action" (for example, check that the disk is properly inserted).

The purpose of these meta-error values is to allow a program to recover when faced with an error code it doesn't understand. You could at least follow the meta-data to have an idea of what type of error it was (error class), where the error occurred (error locus), and what you probably should do in response to it (suggested action).

Sadly, this type of rich error information was lost when 16-bit programming was abandoned. Now you get an error code or an exception and you'd better know what to do with it. For example, if you call some function and an error comes back, how do you know whether the error was a logic error in your program (using a handle after closing it, say) or was something that is externally-induced (for example, remote server timed out)? You don't.

This is particularly gruesome for exception-based programming. When you catch an exception, you can't tell by looking at it whether it's something that genuinely should crash the program (due to an internal logic error - a null reference exception, for example) or something that does not betray any error in your program but was caused externally (connection failed, file not found, sharing violation).

Comments (62)
  1. Somebody says:

    "When you catch an exception, you can’t tell by looking at it…"

    What about:

    try {

    // do stuff

    }

    catch (ExceptionThatShouldCrashProgram e) {

    crashAndBurn();

    }

    catch (ExceptionCausedByExternalFactors e) {

    // do stuff to recover

    }

  2. Chris says:

    You write that you cannot tell by looking at an Exception what kind of error originally occured. Of course you can!

    I’m not familar with .NET programming and I’m definitely not up in raising another Java/.NET battle but since Java is what I’m doing every day I can say that there is a way to tell the cause and severeness of an Exception, by simply looking at the class of Exception that has been thrown

    Let’s consider this small piece of demo code:

    try {

    doStuff();

    } catch(OutOfMemoryError e) {

    System.err.println("Cause of error was not enough memory");

    } catch(FileNoFoundException e) {

    System.err.println("Cause of error was a missing file");

    } catch(ConnectException e) {

    System.err.println("Could not connect to remote machine");

    }

    It’s even a lot more comfortable to add additional information. A FileNotFoundException might be subclassed by, let’s say a DriveUnavailableException (user tried to access K:test.txt where no drive K is available).

    Any old program just knowing the existence of FileNotFoundException continues to work properly, and any new feature might perfom an explicit check for the new kind of Exception.

  3. Ben says:

    It has been my experience that the caller, more often than the callee, knows how severe a particular error is in the current context. Asking the error producer things like "Should we close the program because of this?" isn’t usually a good thing.

    To use the file access error example; if you are trying to open a file and you get a sharing violation, you may just want to default to a read-only state.

    As for what produced the error in the first place, any relevant information should be included in the exception type or members whenever possible.

  4. Ben says:

    >>you can’t tell by looking at it whether it’s something that genuinely should crash the program (due to an internal logic error – a null reference exception, for example) or something that does not betray any error in your program but was caused externally (connection failed, file not found, sharing violation). <<<

    If an exception happens due to external factors, that’s supposed to be handled by your code in any case, with a well-defined clean-up/recovery strategy–that should be recognizable, at least.

    If it’s an internal logic error, at some point an exception or error will be returned, crashing your program because you didn’t plan for buggy code.

    Granted, there are always truly exceptional situations that you can’t plan for that might cause an unforeseen error that is indistinguishable from buggy code.

    In the case of bugs causing "access denied" exceptions, for example, shouldn’t that bug be fleshed out with a decent set of test cases?

  5. Ben (II) says:

    BTW, Those are two different Bens who posted 4 minutes apart. :)

  6. Chris/Ben/Somebody:

    You’re all missing the "Fuzzy Abstraction" effect.

    What happens when my application calls into class A, which calls into class B, which calls into external assembly C, which calls into class D, which calls into external assembly E.

    E throws an exception that’s 100% relevant to its situation – the app doesn’t even know that E’s going to the internet (or floppy, or whatever) to fetch its data.

    Classes A, B and D correctly ignore the exception (they do unwind correctly of course).

    But what does the application do with that exception? It doesn’t know why E was going to the net. It doesn’t know whether or not the circumstances that caused E to go to the net were transient or not.

    All it knows is that an exception’s occurred.

    The only way around this is for classes A, B, and D to catch ALL possible exceptions, reframe them in terms of their own exception hierarchy, and then rethrow. But whenever they do this, they lose information about the exception. By the time the application gets the error, all they see is the equivilant of E_FAIL.

  7. Jim Davidson says:

    Larry,

    Your objection comes down to translation of exceptions at module/application boundaries.

    The answer is that those (few) exceptions that can usefully transmit information across such boundaries should be subclassed and tested for, and then possibly translated.

    Surely exactly the same kind of problem arises when trying to apply a global list of error codes across multiple projects with different sponsors and schedules?

  8. Raymond Chen says:

    Chris: Oops, you forgot ServerClosedConnectionException. Too bad testing didn’t find that case either.

  9. Eric Lippert says:

    For similar reasons, VB6/VBScript’s error codes are backwards-compatible back to the original Altair BASIC code written by Bill & Paul 30 years ago:

    http://blogs.msdn.com/ericlippert/archive/2004/09/09/227461.aspx

  10. Raymond Chen says:

    Chris: Oops, you forgot ServerClosedConnectionException. Too bad testing didn’t find that case either.

    Ben Cooke: "I would expect that a caller would know what it’s direct callee is going to do."

    Okay, what exceptions should the caller of StreamReader.ReadLine() be expecting?

  11. Doug says:

    Don’t you love religion in Programming? Exception handling is one of the "new" religious dogmas. (If you’ve been programming for 25 years.) What is amusing is the "jump to the defense of exceptions" that happens. This then promotes the "pound harder on exceptions" responses.

    Fundamentally, error handling is hard. There is no one method that solves all problems.

    I find that try/catch blocks can make the code even more messy than ever, making it harder to understand what the code is doing, and are all the cases handled. Try/catch forces you to use one of a limited set of algorithms, and it seems to be hard to make "clean" code, whatever that means.

    File IO errors are especially difficult to handle, since the range of possible errors seems to keep increasing over time. At least the Tape I/O errors seem to have left the general lexicon.

  12. Ben Cooke says:

    Raymond,

    That depends on the design of the standardized exception library. For Java’s version of that function, there is one exception called IOException, which everything else can subclass. If a program is just operating on a stream with no idea what kind of stream it has, then it can catch IOException and fail gracefully, because any kind of error while reading is probably a show-stopper. If a specific caller knows it’s really operating on a socket, it can catch SocketException and handle that case in a more special way.

    My point, basically, is that increased abstraction for your call means increased abstraction for (and thus less granularity in) error reporting. The heirarchy of exception classes is just like a heirarchy of error codes, but with arbitrary metadata attached to each depending on the needs of the caller. If you are operating on any kind of stream, then all you can do is know that there was some kind of stream error, because you can’t possibly know about all of the things that can go wrong with all streams. However, you can know that streams can go wrong in various ways and differentiate these from errors such as a bug in the library you’re calling causing a divide by zero, or suchlike.

    In practice, though, Java’s Exception classes are pretty useless when it comes to metadata; most don’t really give you anything beyond a cryptic natural-language message. The heirarchy and standardization are good, though.

  13. Andrew Shuttlewood says:

    > Okay, what exceptions should the caller of StreamReader.ReadLine() be expecting?

    Java has checked exceptions which ameliorate this to some degree.

    public String readLine()

    throws IOException

    Of course, if I happen to readLine() in my code, that doesn’t magically bubble downwards from E to A either, as it would have to be explicitly declared by D, C and B to bubble downwards. D will have to either state that it will bubble downwards or wrap it in it’s own exception type or possibly handle it in a different way.

    Exceptions got mentioned on lambda a

    <a href="http://lambda-the-ultimate.org/node/view/472#comment-3488">couple of days ago </a>.

  14. Andrew Shuttlewood says:

    Erk. Can you fix the link please? Never quite sure how these things will deal with links :|

  15. Vorn says:

    Raymond: all the ones it has in its throw list. A program shouldn’t compile if a caller doesn’t handle or declare as throwable all the exceptions its callee throws.

    Vorn

  16. Ben Cooke says:

    I find that try/catch blocks can make the code even more messy than ever.

    I agree on this point, and certainly don’t think that exceptions are some magic answer to all error-handling needs. My posts thus far were simply intended to address what I saw as an error on Raymond’s part, and later on Larry’s part. Raymond said:

    "When you catch an exception, you can’t tell by looking at it whether it’s something that genuinely should crash the program […] or something that does not betray any error in your program but was caused externally […]."

    …but if you have some standardized exception class heirarchy, you can distinguish between "I tried to read from a stream but some external problem prevented it" and "I tried to read from a stream but this stupid I/O library tried to dereference a null pointer/object".

  17. Raymond Chen says:

    Too bad .NET’s WebException and SocketException don’t derive from IOException, for example.

    But even that wouldn’t fix everything. What about "access denied" on a registry key or service? That’s not an I/O exception but it is something a program should be prepared for.

  18. Karl Koscher says:

    Java makes some distinction between types of exceptions. Anything derived from java.lang.Exception should be caught and dealt with. Anything derived from java.lang.Error is a serious problem and shouldn’t be caught or dealt with. You’re required to handle (or at least re-throw) any Exception a method may throw, but you’re not required to handle an Error.

  19. David Levine says:

    Checked exceptions dont scale or version well; not using them doesn’t scale or version well either. :-)

    Error handling is hard, and using exceptions for it is still hard. It’s better then using error codes, especially since you can catch-wrap-throw up the callstack, adding context as you go.

    There is a fundamental contradiction in error handling. In general, the further away the recovery code is from the site of the exception the less able it will be to correctly deal with the specific problem; it will have full business logic of the context leading to the failure but little technical knowledge on how to proceed with recovery. However, the closer the recovery code is to the failure point the less likely it is to have sufficient business context, resources, or access to new/different data, to be able to recover, even though it has full technical knowledge of the cause.

  20. Ben Cooke says:

    Raymond,

    Failures reading the registry is something hardly any program does well, because it’s usually abstracted away so that the caller doesn’t even know the registry’s involved. Exceptions or not, reporting that error without "giving away" that you’re hitting the registry is a hard problem, unless you want every single function, regardless of purpose, to be able to return an "I wasn’t allowed to do that for some reason" error.

    One reason why people have come to dislike return status codes so much is that functions traditionally can only return one value, so you can’t return an answer and a status code at the same time unless you resort to the technique of using invalid values to indicate errors. For those that love return status codes, there should be a separate "result" value returned from every function in addition to its answer. Of course, no-one wants to do that because of the extra overhead it would cause when in most cases the result would end up getting discarded anyway…

  21. Eric Lippert says:

    I understand the criticism that exception handling makes it harder to write correct code because it’s non-obvious what all possible program flows are.

    However, I don’t understand the criticism that it’s hard to know that you’ve handled all the exceptions you need to handle. As Raymond points out in this very article, it’s equally impossible to know all the error codes that a call could return.

    As a mitigation to this problem — that external components could be throwing any darn thing — exceptions are CLEARLY superior to error codes. Exceptions are extensible, subclassable, nestable information bearing structures that contain not just what went wrong, but where in the code it went wrong, and what error to report to the user. Exceptions, unlike HRESULTs, are self-describing and unique. (A FACILITY_CONTROL HRESULT tells you nothing if you do not know what control returned it.)

    For me, the value proposition for exceptions which makes them worth the pain entailed by the first criticism is a consequence of the first criticism: exceptions, unlike error codes, cannot be accidentally ignored without crashing the program.

    Writing correct error handling code is hard. Hard enough that people often skip it, or write lousy error handling. Code which ignores error return codes tends to muddle on even when its in a bad state. Exceptions, which cannot be ignored, make such programs brittle.

    Brittle is good in the application development world! I want badly written programs to be brittle, so that developers are encouraged to design careful error handling via negative reinforcement. I want programs to die horribly and immediately if there’s an unanticipated problem, not muddle on, making me think its working when in fact it’s doing heaven only knows what.

    Exception-throwing languages force you to think hard about error handling. Anything that forces you to think hard about important problems is goodness in my book.

  22. Tim Smith says:

    Eric: Exception-throwing languages force you to think hard about error handling.

    No they don’t. Not in the least. All they do is change how you ignore the errors. I have seen too many instances where people use "catch (…)" to ignore nasty errors such as access violations.

    I bet most people here have run into bad applications where OnPaint throws an exception, pops up a error message and then throws an exception again when the dialog is cleared and the window tries to repaint. Thank god for task manager.

    In a perfect world, exceptions ARE clearly superior. However, in the real world where programmers are poorly trained and on a tight schedule, exceptions are just another form of the same nightmare.

    The idea that exceptions force you to do anything is absurd.

  23. Boris Zakharin says:

    Then maybe catch(…) should not exist?

    Yes, I admit, I do it too. But in my case, any time a serious exception (i.e. one I don’t specifically handle) comes up, I display an "A serious error has occured. Would you like to attempt to recover?" message, so at least in theory, the user should be able to click "no" to quit if the message comes up over and over again. (On thne other hand, if the user does click "yes" somew fun things happen, such as loosing the DialogProc and being unable to quit)

  24. The first Ben says:

    I’ve seen good arguments for why C# doesn’t have strong exception checking before, and I believe they were right in leaving that out. But I wonder if it would be possible to introduce exception checking that just displays compile warnings?

    It seems a mistake to rely on documentation to tell you what exceptions could come out of a particular method. Why can’t the compiler check this and just let you know? It could still compile the code. It could even be a switch similiar to "Option Explicit" in VB.

    This has probably been discussed many times before and I’ve just missed it.

  25. Re: brittle, extensible (Eric Lippert’s comment):

    Catching unsealed exception types is semantically equivalent to catch(…) since you don’t really know, over time, what’s going to derive from that exception type.

    The categorization of failures problem tends to disallow any single inheritance tree from being able to organize the exception type hierarchy. The extensibility of exception types means that over time the contract of what exceptions you’re catching changes.

    The entity at the top of the conceptual stack (the application) is entitled to screw itself up as much as it likes. Things in the middle are the problem, so in general, you should only catch exceptions originated in your same call frame or the next call frame down. (I’ll ignore "trivial" frames that come with template classes.)

    Throwing exceptions is great. Catching them is terrible.

  26. Raymond Chen says:

    "3. "What you probably should do in response to it (suggested action)"

    Again, from .NET’s Exception class you find this information: – HelpLink, – Message"

    But I haven’t written an English language parser yet. "Message" is great for showing to the end user, but how does the program know whether it should just "wait 3 seconds and retry"? Or should it just offload that to the user? "Unable to save the file due to the following error: Routing tables are being built. Wait three seconds and try again."

    Note that InvalidOperationException includes things that are programming logic errors, like NullReferenceException and ObjectDisposedException. What is the user expected to do when prompted with "Unable to save the file for the following reason: Cannot access a closed stream."?

  27. Ben Cooke says:

    Larry,

    I agree with you to a certain extent, but I would expect that a caller would know what it’s direct callee is going to do and what kind of exceptions it can throw. Therefore accurately "re-framing" the exception in different terms can be done to a sensible extent without the loss of important information.

    However, if the exception *does* bubble up unmodified from E to A, at least A knows that the error had something to do with the network, or a disk, or whatever. Assuming that the exception classes are reasonably standardized (as in Java) and designed in a sensible manner, the original caller A can at least handle some superclass of the exception and use an overridden method to get information that the user might find useful to rectify the situation.

    Of course, in practice applications make up new exception types willy-nilly, so all you really end up handling is plain old Exception, and short of just putting the message inside in a dialog box onscreen there’s not much you can do with that.

  28. CN says:

    > But whenever they do this, they lose information about the exception.

    Why would you lose anything if you keep "causes" with the inner exception. That way, you both have the basic "failure in component I know about" data, but with an easy way to reach in and check out what was really going on.

    I don’t see any way that error codes make this specific scenario easier. The possibility to subclass and wrap exceptions gives, IMHO, a flexible way to keep it simple *and* keep a way to handle specific errors in a custom way. Of course, the simplicity may lead to code that keeps it simple where a more thorough solution is really needed. I think that’s like saying that garbage collection makes resource leakage more frequent because it (sort of) eliminates memory leakage.

  29. Seems like I misunderstood what you meant with "suggested action". I thought you meant the error messages you get in MS-DOS for example during file copy. "Media error. (R)etry, (I)gnore, (A)bort" (just an example error message, probably wrong).

    So I agree that there ain’t anything like "suggested action" in the Exception class(but could be added in a derived class).

    Guess it has been discontinued is because the action to take depends on how fault tolerant the application needs to be.

  30. Muck says:

    While I agree that exceptions are no silver bullet for error checking, and in some cases make things worse, they are usually not as bad as Raymond argues.

    As others have pointed out, some of the issues that are adressed in this blog are weakened or don’t apply at all to checked exceptions.

    ((This is not a pro-Java rant. I have used both Java and C# and must say that I like C# better. But I also like checked exceptions. Here’s why.))

    First, checked exceptions *force* the API designer to have new error conditions derive from old ones. With "Too bad .NET’s WebException and SocketException don’t derive from IOException, for example.", Raymond describes an API design flaw that would not have happened in the first place if .NET/C# had had checked exceptions.

    There are ways to fight against checked exceptions, of course. The point is that these leave visible marks in the source. There’s the empty catch block, and there’s "throws FooException" in the method declaration. The first one loudly screams DANGER to every person reviewing the code. The second one is a bit sneakier; however, a function that "throws IOException" while not doing any IO itself must be considered suspicous as well. At the very least is the "throws" declaration part of the function’s public API and as such is expected to be documented. If it isn’t, you have a bug in the documentation, and probably in the code as well.

    A third way to fight against exception checking in Java are RuntimeExceptions. I think they should have been called UncheckedExceptions, or else been given an equally derogatory name. Then it would be more clear that these are intended for checking things that are actually mild bugs in your code; NOT normal failure conditions that a (hypothetical) correct program would have to prepare for. Like accessing an array outside of its bounds. Of course, mis-use of RuntimeExceptions is also easy to spot (and fix).

    Of course, "they" probably can’t add checked exceptions to C# this late, which is unfortunate but inevitable. As C# seems to combine the worst features of C like error codes (= ability to ignore easily) with those of exception handling (hard to spot code paths), I expect the most of the exception handling proponents to come from a Java background. I could be wrong.

  31. Ben Jr. says:

    Many Microsoft APIs have a well defined system for handling error codes: a boolean return value indicating success or failure. If you want to return anything else from the function, you should pass in a pointer to the return value. This system makes avoidance of return value checking a hazardous proposition since a false return value could mean your pointed-to return value is undefined. It also makes error-checking of sequential statements rather easy by using the short-circuit AND operator. Obtaining extended error information using GetLastError() makes it relatively easy to create a single code path for reporting errors, but the quality of the error information is limited by the number of available bits in the return code.

    I would agree with others on this list that exceptions can provide more detailed information about errors than a single DWORD. Java makes good use of heirarchical exception classes, but sometimes the code can become littered with catch() blocks. In C++, on the other hand, exceptions seem so rare that most times all I see is a top-level catch(…).

    Thinking out loud, one thing most exception systems are missing is resumable exceptions. In both Java and C++, by the time you’ve caught the exception your automatic objects have already been collected. Structured Exception Handling seems close, but due to the types of exceptions raised you’re limited in what you can actually do to recover. Now, depending on what kind of severity you consider exceptions to be, resuming from any exception might be a bad thing (for C++ that is probably the case), but a Resumable exception class that gets thrown when a user could clearly solve the problem (floppy in drive?) might be nice to have.

    P.S. Since this is my first time posting to Mr. Chen’s ‘blog, I’d like to thank him for this great resource. Your attention to detail — particularly with legacy systems — has provided quite an insight into the Win32 API.

  32. Btw, this discussion wouldn’t be complete without including: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/automat/htm/chap11_2nlb.asp

    which is an attempt at providing a facility like the MS-DOS facility for COM objects.

  33. Ben #3 says:

    So what error mechanism do you recommend Raymond? If you could design a language from scratch to return an IErrorData object when an error occurs, how would you design things to make it easier for applications to make decisions?

  34. "Then maybe catch(…) should not exist? "

    No, it is needed. But should only be used at a top-level for handling unknown exceptions not caught anywhere else. For example OutOfMemoryException or similar would be caught here. (Anders Heljsberg explains this better here: http://www.artima.com/intv/handcuffs2.html)

    What can you do with that exception? Log it somewhere, like the event log, and depending on the error recovery needed(forward, backward) you should do something intelligent now. In many situations this might be to show some dialog that something has gone wrong and ask the user if it should be restarted(such as using ReportFault in Win32). Or rollback and try again, try a different algorithm, etc. But exceptions forces you to do something explicitly, which could be something stupid like "catch(…){ /* do nothing */}". With error codes you might forget to check the error code of printf and a lot later see that the application crashes.

    "Too bad .NET’s WebException and SocketException don’t derive from IOException, for example."

    I see no problem with WebException. It derives from InvalidOperationException which fits better, IMHO.

    SocketException could benefit from multiple inheritance, since it is both a Win32Exception and an IOException. Deriving from Win32Exception discloses internal information about the class.

    But isn’t the information you get from an exception similar to the error information you miss from the 16-bit days?

    1. "Type of error it was (error class)"

    This is the exception class.

    2. "Where the error occurred (error locus)"

    In .NET’s Exception class you find these members with this kind of information:

    – Source

    – StackTrace

    – TargetSite

    You might find more details in sub-classes.

    3. "What you probably should do in response to it (suggested action)"

    Again, from .NET’s Exception class you find this information:

    – HelpLink

    – Message

    But obviously it is important that you think about where exceptions are to be catched. You don’t have that choice with error codes. And there will be exceptions you get that you didn’t think about first, but that applies to error codes as well.

    Finally, Larry Osterman wrote in a response: "The only way around this is for classes A, B, and D to catch ALL possible exceptions, reframe them in terms of their own exception hierarchy, and then rethrow. But whenever they do this, they lose information about the exception. By the time the application gets the error, all they see is the equivilant of E_FAIL."

    Nope, that’s what InnerException. You should have all the information about the failure in the exception. Otherwise it is _bad code_ and the developer should read about exception handling. Let’s say that class A eventually gets a RemotingException. This will contain the exceptions from the other classes, maybe a WebException from class E etc. This is a lot more information then you get from E_FAIL :) What can you do with that information? Maybe decide that the webservice in E is down at the moment and try to use a different… If class A only gets something like E_FAIL it doesn’t know that it was class E which failed, could’ve been class D instead.

  35. Goran Pušić says:

    Larry Osterman wrote:

    >You’re all missing the "Fuzzy Abstraction" effect.

    >

    >What happens when my application calls into class A, which calls into class B,

    >which calls into external assembly C, which calls into class D, which calls into

    >external assembly E. E throws an exception that’s 100% relevant to its situation –

    >the app doesn’t even know that E’s going to the internet (or floppy, or whatever)

    >to fetch its data.

    Hmmm… Let me try the same, but in a slightly different way (warning: irony ahead):

    What happens when my function calls into Win32 API A, which calls Win32 API B, which calls … you get the drill… into Win32 API E. E returns FALSE and sets last error that’s 100% relevant to its situation – but my function doesn’t even know that E’s going to the internet (or floppy, or whatever) to fetch its data.

    Tell me that I’m wrong, but typically, D, C, B, A , if they see FALSE return from E, D, C, B, simply clean-up and return FALSE, too (they "correctly ignore the exception"). So, in your example, there is no practical difference between "exceptions-code" and "error-return-code". (The same goes for Raymond’s last paragraph).

    So, what I’m saying is this: people are complaining that not enough info is available from exception when it is caught. But, info is not available from error-return, too! Well, at least not in the code I’m working on on a daily basis, and I dare you to say that you have different situation. And methinks meknows why: because, in order to provide "enough" error information with either "error-return-code" or exceptions, one needs to write A LOT more code, and one is usually not doing this. So…

    There is one upside to exceptions, though: there is less error checking and more "meat", i.e. less noise when reading the code to find out what it does in the normal course of operation. And, this is more important! We write code to do stuff, and not to cope with errors. (It has to cope, but that is NOT the MAIN concern).

    The downside is in Raymond’s prevoius post (let’s call this "invisible exception problems"). But, methinks, we need to be trained to spot these. Hehe, I know I feel pain in my stomach when I see problems like this and I have to work on something else (its probably some rare neural system disease, it does not seem that many other people suffer from this :-))

    Also, note that that recent programming frameworks do not endorse error-return as error-handling strategy anymore? (Let’s see… What we had recently… Java, VCL, .NET… Nope, no BOOL DoStuff(params) in any of those). Mesay, resistance is futile. Learn and live with it, because they will not dissapear (at least not Java and .NET :-)).

    (Note to C++ people: read Herb Sutter’s books. I will do it, too :-) ! I’m going to ask for money for books from my boss immediately!)

    Goran.

  36. Goran: Agreed, except that exceptions may persist this information across boundaries. An example is an exception during remoting in .NET: you get a stack trace from both sides of the call. Guess you get this with most RPC solutions available, like java’s RMI.

    Larry: I find it better explained (together with most recent error handling mechanisms used in Windows) in Chris Brumme’s article "The Exception Model" here http://blogs.msdn.com/cbrumme/archive/2003/10/01/51524.aspx. Obviously this is written from the opposite point of view then this blog :)

  37. Goran Pušić says:

    Andreas Häber:

    Yes, stack trace is very nice thing to have. We poor unmanaged C++ poeple don’t have it though (snif!). Stack trace provides excellent "context-info" for support people (assuming they can get hold of it from some log or whatever). Error-return code cannot provide that (well, at least, not out-of-the box; may be a theme for Raymond – please tell us how to get something similar from PDBs etc?).

    OK, that’s it, I’m moving to .NET programming! ( I can already hear my boss: "Yeah, you wish, you lazy bastard! Write these ifs for our existing unhandled error-returns first!" )

    Goran.

  38. DrPizza says:

    "This is particularly gruesome for exception-based programming. When you catch an exception, you can’t tell by looking at it whether it’s something that genuinely should crash the program (due to an internal logic error – a null reference exception, for example) or something that does not betray any error in your program but was caused externally (connection failed, file not found, sharing violation). "

    So you can’t catch std::logic_error&, std::runtime_error&, or whatever other subclass you want?

    News to me.

  39. Chris Becke says:

    The problem – a problem perhaps – really boils down to, someones exceptional condition is someone elses normal flow control.

    That is, exceptions demand special syntax to catch and process. Which makes them different to normal "if error then" type flow control.

    Which means that, In an exception "enabled" environment you will frequently end up with some muppet of a module throwing exceptions for what a caller would like to treat as a non exceptional – i.e. handled with traditional flow control – condition.

    So you end up with two entirely different sytles of flow control interleaved.

    For the most part, most programmers, when exposed to exceptions, get very excited and start creating all manner of new exception types due to an utter lack of solid guidelines for determining what sort of program event should be categorized as an exception, and what should not.

  40. Dmitri Toseland says:

    Chris, I’d see that as the crux. After all, most applications need to use both exception-based and return-value-based error handling. There is a great lack of good writing out there about how to arrive at a good error handling policy for large applications made out of several modules. Not too surprising, since people only really started getting a good grip of how to use exceptions in C++ about 6 years ago or so.

    But some exception-handling patterns are fairly well known by now. For example, using exceptions instead of for-loops or other standard control flow mechanisms is not a good thing. Also, "catch" should be rare, but "finally" (Java/C#) or releasing in destructors (C++) should be common. Also, it’s not necessary for every exception to have its own subclass, but it’s very useful for every exception to have a common base class. Also, the lowest-level API for something like a stack shouldn’t combine mutators and accessors (seperate operations like "pop" and "top").

    So it’s not like there’s nothing out there nowadays – this is largely a matter of education. There may be a fuzzy line at the boundary between where exceptions and error-returns should be used, but it’s not all that fuzzy. There are many large projects out there that have this kind of issue pretty well sorted out, and as a result have error handling that is clear, comprehensive and robust.

    However, you need to be prepared to alter interfaces to a module as its role in a system changes. For instance, exception semantics for error handling may make sense when a module is first written. Later on, it ends up being used as a base for s wide range of clients, some of which require error-return semantics. So you then might provide a new interface which gives such semantics. No-one gets all aspects of interfaces right to start with, and error handling should be revisited as much as any other aspect of an interface.

    Incidentally, this is why error-return is the way to go for low-level APIs that are to be used by a large number of clients in the field. Examples are DOM, sockets, Win32 and so on. Since you can build an exception layer on an error-return layer, but can’t do the reverse nearly so well, and you can’t change the interface once it’s out, it makes more sense to put out the lowest-level API with the lower-level of error handling semantics.

  41. Raymond Chen says:

    DrPizza: Okay, so you caught a std::runtime_error&. Now what? Should you retry up to three times? Prompt the user before retrying? Fail the operation? Abort the program?

  42. Mike Dahmus says:

    Larry and others:

    Actually, Java’s model is even better than others have let on – if you’re writing an API level class (say class D which uses class E which throws the original exception), you can write your own Exception class which stores the original exception from the library function in E. In fact, I see this quite frequently in code I use.

    So when debugging on your end (the guy who calls D or some variant of such), you can see D’s interpretation of the exception, plus, the original exception from E if you want it, as in:

    public class DLibraryException extends Exception

    {

    private ELibraryException rootCause;

    public ELibraryException getRootCause()

    {

    return rootCause;

    }

    }

    you’d want a bunch more stuff in D to handle other possible sublibraries and handle its own case, but you get the picture I’m sure.

  43. Paul Spendlove says:

    Q.1) Is catch(…) like catching unsealed exceptions?

    A.1) No. When you are handling in a catch(…) clause, you have *no* information about what the exception is. You either swallow it and lose all exception information, or log the fact that "some kind of exception occured" and rethrow it. That’s all you can do. But when you have a typed exception, even if the true class is unknown, you can often use a virtual function or some kind of RTTI to at least get at the true type of the exception.

    Q.2) Is catch(…) useful sometimes?

    A.2) Yes. If you’re about to leave C++ and go into another language or environment (COM, .NET, Java, Excel, Python, etc…), exceptions can’t propogate. So you have to have a last-ditch handler that even copes with exceptions you don’t know about.

    Q.3) Are Java checked exceptions good or bad?

    A.3) They’re both. They’re good because they provide a guarantee of exactly which exceptions to expect. They’re bad because your exceptions then become part of the signature of your function, leading to later problems when you refactor things. I’ve seen Java developers go either way on which effect is more important.

    Q.4) When is a good example of when exceptions are *not* good for error handling?

    A.4) When you’re providing a low-level API for use by a range of clients, for instance a cross-platform sockets layer. Some clients will expect connection failures and retry across a range of potential servers. But then again, some clients will always throw an exception on connection failures, as they will be hosed. The best way to provide both sets of behaviour is via an API/error code approach.

    Q.5) When is a good example of when error codes are *not* good for error handling?

    A.5) When you’re providing a generalised set of integration routines for valuating financial derivatives. Sometimes models will fail to calibrate or converge. Providing error codes obfuscates the flow of the maths, while offering no benefit, since all one can typically do is release the resources and relay that the attempted mathematical operation was impossible.

    Q.6) So it’s not some kind of either-or thing between exceptions and error codes? They both have their uses in different areas?

    A.6) Yep.

  44. Old Fart says:

    Exceptions are essential a COME FROM, popularised by languages like INTERCAL on the basis that they helped make any code that uses them incomprehensible. This certainly seems to be the case in much exception-using code that I’ve looked at, where it’s often necessary to spend a great deal of time figuring out exactly what it’s going to do under different situations.

  45. Cooney says:

    Stuart:

    > Obviously it’s a good idea if someone catching a FileNotFoundException can determine what the file is

    Perhaps you could start with the file you just attempted to open?

  46. Someone says:

    I don’t see many programs translating error-codes, unless when it is to make the error less_informative. Usually, the intermediate function does a clean up and relays the error-code to its caller, and that goes all the way up until the user gets that annoying E_FAIL and has no idea what the heck went wrong.

    So I don’t see the point on saying one looses information with exceptions.

    Of course exceptions don’t do things automagically, but it’s much easier to enrich them with useful information than with plain 32 bit codes. For instance, with exceptions you could capture the callstack to give some context, so you don’t need to translate it everywhere. And if the program can’t recover and needs to throw the error at the user, you could serialize the exception and try to match it against a knowledge base to get better information. Now try to do that with an HRESULT and what you get is something like this: http://www.microsoft.com/windows/windowsmedia/mp10/errors.aspx#c00d11cd_0x00000000

    Are exceptions harder to program than error-code based software? Of course! But so is OO compared to procedural programming. And so is Calculus compared to basic Arithmetic. These things are more complex, but they allow for the solution of whole new classes of problems. As Robert C. Martin says, it takes complexity to manage complexity.

  47. Scott McCaskill says:

    "This is particularly gruesome for exception-based programming."

    Huh? How does it follow that this problem (information loss in error reporting) is somehow inherently worse for exception-based error propagation?

    There are legitimate arguments one can make in favor of using error codes rather than exceptions for error propagation, but I don’t see how this could be one of them.

  48. Raymond Chen says:

    Win32 uses both error codes and exceptions. Error code for nonfatal errors and exceptions for fatal errors. That’s how you can tell the difference. But if your only error model is exceptions, then you have to come up with some other way of determining whether a particular exception is fatal or nonfatal.

  49. DrPizza says:

    Win32 throws SEH exceptions for non-fatal errors.

    So, no, you can’t tell the difference.

  50. Raymond Chen says:

    That InitializeCriticalSection raises an exception in low memory is just a design flaw. But it turns out that you can’t catch it anyway since the exception is not raised in an exception-safe manner! (The critical section object is left in a corrupted state.) So you can’t catch it and do anything meaningful. End result is the same: Don’t catch it.

  51. Paul Spendlove says:

    Well, surely it depends on what software you’re writing. That having been said:

    1) If you know about classes inheriting from std::runtime_error, you use dynamic_cast<> to check for them and handle any errors appropriately.

    2) If it’s not that, then it’s an actual std::runtime_error, or some other subclass of it that you don’t know about. You recover back up the stack to whatever level knows enough about your process to continue with the next task, or to restart, or whatever makes sense for your particular application.

    3) So, for instance, if you’re processing trades in a batch, you might mark this one as "bad", together with the text string in the exception and any subclass information you *have* been able to get, and then you continue onwards.

    4) Of course, your error string will generally include, generated by a standard macro everyone uses instead of "throw":

    – the file and line the exception was thrown at

    – the name of the function (on platforms supporting __FUNCTION__)

    – nested exceptions that may have been added at intervening levels

    …so a quick investigation will often pinpoint exactly what happened.

    5) This seems superior to trying to decipher a numeric return value (while praying that no intervening level of control has changed the value, of course).

    Am I missing something?

  52. It’s certainly possible to put an "enum SuggestedAction" into the next version of the BCL and provide that facility in all exceptions.

    It also would certainly be possible for the framework to have chosen a more coherent hierarchy of exceptions so that catching a base class was useful more of the time.

    And checked exceptions certainly help; I’m still holding out hope for a ThrowsAttribute in future versions of C# so that at least checked exceptions can be *optionally* enabled.

    But one thing that bugs me like crazy is exception types that don’t use Message for a human-readable error message. FileNotFoundException puts the filename that couldn’t be found into Message. Why??? Obviously it’s a good idea if someone catching a FileNotFoundException can determine what the file is, but it would have been easy to have a FileName property on the exception for that purpose. Using Message for that purpose makes it impossible to write code like:

    try {

    DoLotsOfStuff();

    } catch (Exception e) {

    DisplayMessage("An unexpected error occurred: " + e.Message);

    }

    Message should be a (localized or localizable) *message* that the program can display to the user, otherwise it’s entirely useless. Currently the semantics of Message are essentially "some arbitrary string that may be useful in figuring out what happened as long as you know what the thrower of this particular Exception subclass puts there".

    And this kind of thing can’t be fixed without breaking backwards compatibility. Grrr!

  53. Timothy Fries says:

    > Perhaps you could start with the file you just attempted to open?

    But you don’t *know* what file you just attempted to open when you call something seemingly unrelated to files on Class A, which calls Class B, which calls Class C, which calls Class D, which calls Class E, which may decide to use a local cache, or hit the network.

    I’d argue though, that if exceptions are being allowed to leak out that expose inner implementation details, that’s a design failure. (After all, Class D should recognize the FileNotFoundException and *know* what the path of resolution is.) But then we’re back to the whole checked vs. unchecked exception debate.

  54. Goran Pušić says:

    Raymond:

    >But if your only error model is exceptions,

    >then you have to come up with some other way

    >of determining whether a particular exception

    >is fatal or nonfatal

    Let me try this slightly differently:

    But if your only error model is error codes, then you have to come up with some other way of determining whether a particular error code is fatal or non-fatal.

    Why I say this: in Win32 APIs, when are SEs thrown? Frankly, I don’t know of any other reason but errors in my code.

    (Exception to this: InitializeCriticalSection, which, as you think, has a design flaw (Thanks! I agree with this!)).

    So, actually, it’s this: we should treat SEs as just a consequence of bugs in our code. Thus they are NOT an error-handling mechanism, but rather a bug-reporting mechanism.

    As far as I can see, in Win32 API, error-returns are the only way to treat execution errors, so "you have to come up up with some other way…". You seem to suggest that SEs somehow report "fatal" errors (as opposed to "regular" errors reportd by error returns), but all I see is that they report my bugs!

    Or am I missing something? I.e what is the number of APIs that actually use SEs to report errors not caused by bugs?

    Goran.

  55. Raymond Chen says:

    Correct, in Win32, exceptions are primarily a bug-reporting mechanism. That’s why you should rarely if ever try to catch them.

  56. Raymond Chen says:

    DrPizza: How do you know that the access violation didn’t come from operator<<? Maybe somebody closed std::cout while you weren’t looking?

  57. What we’re realling missing from exception handlers, user friendly relevant meta-data for suggesting ways to fix the problem.

  58. DrPizza says:

    Because I didn’t enable iostream exception throwing, so the faulty write will just set failbit.

    And of course in real code I’d turn on SEH translation and catch a C++ exception instead of an SEH exception, so I could in any case tell the difference.

  59. DrPizza says:

    This code has no real error and no real bug, and throws a perfectly catchable SEH exception:

    __try

    {

    while(true)

    {

    int* ptr(reinterpret_cast<int*>(rand());

    std::cout << *ptr << std::endl;

    }

    }

    __except(GetExceptionCode() == STATUS_ACCESS_VIOLATION)

    {

    std::cerr << "oh well." << std::endl;

    }

    It mightn’t be particularly useful, but useful programs can do similar things (and are forced to operate in a similar way, because IsBadXxxPtr is useless).

  60. Joku says:

    EMD wrote: "What we’re really missing from exception handlers, user friendly relevant meta-data for suggesting ways to fix the problem. "

    One possibility is that, if there’s internet connectivity on the machine running the app, when exception is thrown, the "chain" of exceptions thrown and the context would generate some sort of unique id, this would then be matched against some online database where the "ways to fix the problem" are described and could also be added. Think of something like this comment on the this blog. The initial blogging is the exception on app, and my comment is the "way to fix" the problem.

  61. Sandbender says:

    Augh, sorry about the formatting — the quoted text was indented here and there’s no preview button.

  62. Sandbender says:

    Larry Oosterman:

    What happens when my application calls into class A, which calls into class B, which calls into external assembly C, which calls into class D, which calls into external assembly E.

    E throws an exception that’s 100% relevant to its situation – the app doesn’t even know that E’s going to the internet (or floppy, or whatever) to fetch its data.

    Classes A, B and D correctly ignore the exception (they do unwind correctly of course).

    But what does the application do with that exception? It doesn’t know why E was going to the net. It doesn’t know whether or not the circumstances that caused E to go to the net were transient or not.

    All it knows is that an exception’s occurred.

    The only way around this is for classes A, B, and D to catch ALL possible exceptions, reframe them in terms of their own exception hierarchy, and then rethrow. But whenever they do this, they lose information about the exception. By the time the application gets the error, all they see is the equivilant of E_FAIL.

    This is actually my biggest problem with the C++ exception model. Exceptions aren’t, by default, checked, and the C++ exception specifier mechanism is somewhat broken in that throwing a subtype instance of a declared exception isn’t allowed. I presume one can throw a pointer to a subtype, as long as it’s thrown as a declared pointer type, but allocating exceptions on the heap seems like a potentially-leaky situation:

    int someFunctionThatThrows () {

    // …

    throw new SomeException ();

    }

    And elsewhere:

    try {

    someFunctionThatThrows ();

    } catch (…) {

    std::cerr << "Unhandled exception." << endl;

    // IMPORTANTLY: the ‘new SomeException’ instance is not deleted!

    }

    Furthermore, C++ compilers don’t, as a rule, check for unhandled declared exceptions, and there’s no rules about what exception types should be checked vs. unchecked as in Java.

    Java’s unified exception heirarchy is a fairly elegant solution, but it goes against C++’s "only pay for what you use" philosophy by requiring programs that want to declare their own exceptions to inherit from, and therefore "pay the cost of", the full java.lang.Exception (and Throwable) classes.

    Raymond Chen:

    Chris: Oops, you forgot ServerClosedConnectionException. Too bad testing didn’t find that case either.

    Ben Cooke: "I would expect that a caller would know what it’s direct callee is going to do."

    Okay, what exceptions should the caller of StreamReader.ReadLine() be expecting?

    Whatever StreamReader.readLine () is declared or documented to throw.

    (Note that my impression is that C# doesn’t do Java-style exception checking. This may be wrong — should research it.)

    Ben Cooke:

    Raymond,

    That depends on the design of the standardized exception library. For Java’s version of that function, there is one exception called IOException, which everything else can subclass. If a program is just operating on a stream with no idea what kind of stream it has, then it can catch IOException and fail gracefully, because any kind of error while reading is probably a show-stopper. If a specific caller knows it’s really operating on a socket, it can catch SocketException and handle that case in a more special way.

    Raymond Chen:

    Too bad .NET’s WebException and SocketException don’t derive from IOException, for example.

    This is a failure of .NET’s exception heirarchy, I think, not a problem with the use of exceptions itself. If there is a way to use a socket or some piece of a socket in exactly the same way as you’d use a stream, and it’s declared to conform to the stream interface (including IOException), then it should never throw an exception that’s not an IOException. In Java, SocketInputStream and SocketOutputStream (which are the two streams available for each open socket) both conform to the InputStream and OutputStream interfaces, but their operations still only throw IOExceptions — even for things like ConnectionResetByPeerException.

    Conceptually, SocketExceptions *are* a failure during I/O, so this even makes perfect sense.

    But even that wouldn’t fix everything. What about "access denied" on a registry key or service? That’s not an I/O exception but it is something a program should be prepared for.

    The API that interfaces with the registry should declare the exceptions it could throw and the documentation for the interface should outline the circumstances for each exception. If you have an implementation of the stream interface that talks to the registry, it should frame failures *during stream operations* in terms of IOException, and failures in methods specific to the hypothetical RegistryOutputStream class in whatever terms are appropriate. For a general interface, the interface should throw a very general exception type which implementations can subclass.

    And "access denied" in the middle of an I/O operation certainly is an I/O exception.

    Tim Smith:

    In a perfect world, exceptions ARE clearly superior. However, in the real world where programmers are poorly trained and on a tight schedule, exceptions are just another form of the same nightmare.

    The nightmare here is that we tolerate "enterprise" systems and consumer-deployed applications that are written by "poorly trained" programmers.

    Michael Grier:

    Catching unsealed exception types is semantically equivalent to catch(…) since you don’t really know, over time, what’s going to derive from that exception type.

    Augh! No! Exception subclassing is in no way semantically equivalent to catching an unknown exception, if the exception heirarchy has had any thought put into it. This (obviously) ties back to Tim Smith’s comment about tight schedules and poorly-trained programmers, but a failure to use a tool well does not make the tool unusable.

    Raymond Chen:

    DrPizza: Okay, so you caught a std::runtime_error&. Now what? Should you retry up to three times? Prompt the user before retrying? Fail the operation? Abort the program?

    There is a running thread in Raymond’s comments that can be expressed as "any exception should have only one correct way to respond." Certainly any one application should respond consistently to errors, but (to pick an example out of thin air) ConnectionResetByPeerException could be considered a fatal error for one application (say, a file transfer program that connects, delivers a file, and disconnects) and a recoverable error for another program (which could decide to reconnect). A third program may not care specifically *why* the exception failed.

    Chris Becke:

    The problem – a problem perhaps – really boils down to, someones exceptional condition is someone elses normal flow control.

    That’s a remarkably apt summary of exceptions.

    David Levine:

    Error handling is hard…

    Yep.

Comments are closed.

Skip to main content