Exceptions and Error Codes


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">I see href="http://primates.ximian.com/~miguel/activity-log.php">Miguel has some
comments on href="http://primates.ximian.com/~miguel/all.html#09%2F29%2F2003+12%3A00%3A00">exceptions
that I must disagree with… "urn:schemas-microsoft-com:office:office" />


style="FONT-SIZE: 10pt; COLOR: black; FONT-FAMILY: Verdana">The beauty of
returning a pointer to an object, is that you can return NULL as your standard
error return value, and if the value mattered, you will most likely take the
necessary steps to check its return value, or they application will just crash
the moment you try to use it.


style="FONT-SIZE: 10pt; COLOR: black; FONT-FAMILY: Verdana">This could be
applied to the .NET and Java APIs to reduce the amount of required try/catchs.
For example, the File.IO.OpenRead method could be (shown here is the artist’s
representation):

  public static FileStream OpenRead (string file)
  {
         int fd = open (file);
         
         if (f == -1)
                 return null;
         return new FileStreamFromFD (f);
  }

style="FONT-SIZE: 10pt; COLOR: black; FONT-FAMILY: Verdana">Which makes null a
valid return value. If you fail to check for the return value, you would get a
nice NullReferenceException, but you would allow the expensive creation of an
exception object, and the ugly try/catch block in the call site.


style="FONT-SIZE: 10pt; COLOR: black; FONT-FAMILY: Verdana">Obviously we can not
change the existing APIs, but we could encourage developers to minimize their
use of exceptions. In my limited personal experience with software design, when
I have faced applications with tons of Exceptions it was extremely hard to keep
up with them. A rewrite of the software to avoid exceptions and use the more
sane API paid for.


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Here is the rationale for exceptions
rather than error codes for exceptional
situations. (thanks to Jason Clark for writting these up for
us)  


face=Verdana>There are many benefits to exception handling as compared to return
value based error reporting. In fact, the Framework exposes more of the benefits
of exception handling then some other common exception models such as Win32 SEH
or C++ exception handling. Good managed API design does not restrict the
application developer from realizing the benefits of exceptions. The following
are some of these benefits.


size=2>Exceptions Promote API Consistency


face=Verdana>Consistency of API is important to developers.  Exceptions
promote consistency, because they are designed to be used for failure reporting
(and nothing else).  In contrast, return values have many uses of which
failure reporting is only a subset.  For this reason, it is likely that
APIs that report failure through return values will find a number of patterns,
while exceptions can be constrained to specific patterns.  The Win32 API is
a clear example of this inconsistency through BOOLs, HRESULTS, and
GetLastError() among others.


size=2>Exceptions are Compatible with Object Oriented
Features


face=Verdana>Object-Oriented languages tend to impose constraints on method
signatures that are not imposed by functions in functional programming. 
For example, constructor methods, operator overloads, virtual methods, generic
methods, interface methods and properties all resolve to functions, and yet the
developer writing the method has no choice in the return value (if any) of the
method.  For this reason, it is not possible to standardize on return value
based error reporting for object oriented APIs. An error reporting method, such
as exceptions, which is out of band of the method signature is the only
option.


name=Near_Failing_Code>With Exceptions,
Error Handling Code Need not be Near Failing Code


face=Verdana>With return-value based error reporting error handling code is
always very near to the code that could fail.  However, with exception
handling the application developer has a choice.  Code can be written to
catch exceptions near the failure point, or the handling code can be further up
in the call stack.


face=Verdana>With Exceptions, Error Handling Code is More
Localized


face=Verdana>Very robust code that reports failure through return values tends
to have an if statement for every functional line of code.  The job of
these if statements is to handle the failure case.  With exception oriented
code, robust code can often be written such that a number of methods and
operations can be performed with the error handling logic grouped at the end of
the try block (or even higher in the call-stack). 


style="BACKGROUND-COLOR: #e9fdf3" face=Verdana size=2>A common argument is that
exception handling code is unsightly.  This argument is usually being made
by someone comparing well written exception handling code with return value
oriented code that is not sufficiently checking return values of
functions. 


Exceptions Are
Not Easily Ignored


face=Verdana>Return values can be easily ignored, and often are. Exceptions, on
the other hand, take an active role in the flow of your code.  This makes
failures reported as exceptions difficult to ignore, and improves the robustness
of code.
For example the Win32 API CloseHandle() fails very rarely, and so it
is common (and appropriate) for many applications to ignore the return value of
the API.  However, if any application has a bug which causes CloseHandle()
to be called twice on the same handle, it would not be obvious unless the code
was written to test the return value.  A managed equivalent to this API
would throw an exception in this case and the unhandled exception handler would
log the failure and the bug would be found.


size=2>Exceptions Allow for Unhandled Exception
Handlers


face=Verdana>Ideally, every application is written to intelligently handle all
forms of failure.  However, this is not realistic as all forms of failure
can’t be known.  With return value oriented code, failures that are
unexpected are ignored by code and the application continues to run with
undefined results.  With well written exception based code, unexpected
failures eventually cause an unhandled exception handler to be called. 
This handler can be designed to log the failure, and can also make the choice to
shut down the application.  This is far preferable to running with
indeterminate results, and also provides for logging that makes it possible to
add more significant error handling for the previously unexpected case. 
(Today, Microsoft Office uses an unhandled exception handler to gracefully
recover and re-launch the application as well as send error information to
Microsoft to improve the product.)


style="BACKGROUND-COLOR: #e9fdf3">You should
only have a custom unhandled exception handler, if you have a have specific,
app-oriented work to do in the handler. If you just want error reporting to
occur, the runtime will handle that automatically for you and you do not need to
(and should not) use a UEF. An application should only register a UEF if it can
provide functionality above and beyond the standard error reporting that comes
with the system and runtime.


size=2>Exceptions Provide Robust Error Info for
Logging


face=Verdana>Exceptions provide for a consistent error handling mechanism, and
as such make it possible to have consistent logging of error information. 
The System.Exception type in the Framework encapsulates stack-frame information
as well as other useful information such as a nested exception type.  This
is great for error logging and discovery of unexpected error cases during the QA
phase.  Exceptions are objects, so it is possible to extend exceptions to
add additional information.


class=heading4char1char>Handling Errors
Consistently and Thoroughly are Essential for Creating a Good User
Experience


face=Verdana>The application developer needs to provide specific information
about what error occurred, why it occurred, and what can be done to mitigate it.
This requires detailed and specific error returns from API calls. The exception
mechanism allows detailed error information to be created by the API developer.
There is no need for the API developer to change global header files to add new
error codes. The API developer can customize the exception subclass as much as
necessary to transmit all of the details of the error to the
caller.


size=2>Exceptions Promote
Instrumentation


face=Verdana>Exceptions are a well-defined method-failure model.  Because
of this, it is possible for tools such as debuggers, profilers, performance
counters, and others to be intimately aware of exceptions.  The PerfMon
application, for example, keeps track of exception statistics.  In the
future automatic instrumentation from profiling to auto-documentation will be
more sophisticated related to exceptions.  Methods that return failure do
not share in the benefits of instrumentation.


Exceptions Unify
the Error Model (Hard and Logical Errors)


face=Verdana>Exception handling or interrupts are the only option for hard
errors such as null references and divisions by zero (certain operations have no
return-value means of reporting failure).  Using exception handling for API
and logical failure reporting is beneficial in that it unifies error handling
for all failure types.

Comments (24)

  1. Frank Hileman says:

    Failing to open a file is not an exceptional situation — it happens all the time! It should not throw an exception — Miguel is right. The real problem is distinguishing "exceptional" situations. When it is done poorly, code is littered with catch statements, and debugging becomes hell.

    For example, I have an VS add-in as part of a designer/add-in solution I am working on. VS will catch all exceptions eventually, so to notice exceptions, I have to set the debugger to break as soon as the exception is thrown. But there are some APIs that are designed to throw exceptions instead of using return codes or values! Since the exception type may be very generic, I cannot simply ignore that type of exception. So here I am, breaking on the thrown exceptions from, for example, CommandBarControls.Item property, which throws an exception every time and invalid name is passed. But this is the only way to determine an invalid name, and I must do this on every run. Debugging is a nightmare. I can turn off the exception breaks. But then I have to turn it back on again when I need it. Usually I turn it off and forget, which means some exceptions may be swallowed without my awareness.

    I will give another example: Int32.Parse. Why should this throw an exception when given an invalid string? It causes the same kind of debugging problem. Fortunately there is Double.TryParse, but no one thinks of looking at that to parse an int. I found it by accident.

    In general, no function should throw an exception unless something very bad, or very unexpected, happens. User input is essential random, so no exceptions should be thrown when parsing. File systems change constantly, so exceptions should not be thrown when accessing files. Someone needs to define the term "exceptional" more precisely.

  2. Dmitriy Zaslavskiy says:

    Brad,
    I don’t disagree with you on the use of exceptions. I do think that this should be the default behaviour for most functions, However there are plenty of functions in .NET for which I would like to see an overload that returns error value, null etc.
    Same goes for opening file. 100% default version should throw an exception, however it would be nice to have a version of File.Open that returns null instead.

    It is also would be a good subject for your API review:
    Should we provide an overload that takes ‘bool throw’ or create a totaly new function for exampe: TryOpen()

  3. Keith Hill says:

    Regarding this statement –

    "An application should only register a UEF if it can provide functionality above and beyond the standard error reporting that comes with the system and runtime."

    Hmm, we install unhandled exception reporters so that our app can either create a minidump or at least mail back a stack trace to the dev team. I know XP has a fancy Error Reporter but I have yet to figure out how an ISV can get failure reports on their applications without paying for Windows Logo certification. Besides that, many of our customers still run Win9x, NT4 and Win2K which I don’t believe have that Error Reporter.

  4. Fran Knebels says:

    I can’t disagree with the first poster more. If I try to open a file, and for whatever reason, the file is not open is an exceptional situation. I like richter’s definition of an exception in his book. "An exception is the violation of a programmatic interface’s implicit assumptions." My assumption in this example is that if I call File.Open, an open file will be returned to me. If something else happens (for whatever reason) throw an exception. Same with Int32.Parse. My assumption is if call this function an Int32 will be returned.

  5. Frank Hileman says:

    Fran: in the case of a file open operation: if your program assumes that the file will be opened, and it is not, then from the perspective of that specific program, it is an exceptional situation. In that case, it is convenient to have a function that throws an exception. But, if you are only given a function that returns null when the open fails, you can throw your own exception.

    If another program, when opening a file, does not assume that the open will succeed (since it can always fail), then failure to open is not an exceptional situation. In that case, if we only have a function that throws an exception, the API is flawed by design, because we must catch an exception, even when it is not an exceptional situation.

    So "exceptional situation" is in the eye of the beholder. However, given a choice of a single function for an operation, it may be better for the API designer (MS) to provide the function that does not throw an exception: the exception throwing behavior can occur at a higher level, and code which needs a return result can use it without catching an exception, which causes debugging hell and extra lines of code.

    It is best to provide both versions, when both situations are common.

    Regarding Parse: in some situations it is used to process data files which can be assumed correct, so I can see your argument. But it is very often used to parse user input, which can never be assumed correct. In that circumstance it is never an exceptional situation to fail to parse.

    So I agree with you in a sense. It is the definition of "exceptional situation" that is so tricky — and the "most common" scenarios. I would prefer non-exception throwing functions for at least these two cases (open and parse). Being stuck with an exception, when you need a return value, is an ugly situation.

  6. Frank Hileman says:

    One more note about file open: best to have an OpenIfExists function that does not throw an exception, since the file can disappear between the existance check and the open.

  7. Shane King says:

    The solution, in my opinion, is to make it easier to turn an exception into a return value.

    If I don’t want an exception, I want null, I have to write code something like:

    FileStream fs;
    try
    {
    fs = new FileStream("blah.txt");
    }
    catch
    {
    fs = null;
    }

    When really, there should be a construct that wraps all that. If c# supported macros (like lisp etc), we could even write our own, but I digress. What I’d like to see is to be able to write it something like this:

    FileStream fs = Catch(new FileStream("blah.txt"), null);

    Making dealing with exceptions a one liner, which saves typing, makes the code clearer, and allows one to use "declaration is initialization".

    If the compiler/runtime was really smart, it might even be able to short-circuit normal exception semantics and make this case perform faster than the general try/catch case, although I’m not sure that’s possible with the .NET exception mechanism.

  8. Bo Yang says:

    The real problem with exceptions, is that it has to exist side-by-side with the traditional return-an-error-code approach. Now the programmers have to deal with two mechanisms of error reporting. Of course you can say that’s a problem with the return-an-error-code approach too, but that approach got here first. Exceptions came later, it’s not really compatible with the return-an-error-code approach, yet it incurs additional burdens on the programmers.

    And really, I don’t see why localizing and grouping error handling codes together is an advantage. Error handling code need not be near failing code ? Of course it needs not to be, but why would you want to put it far away ? As far as I can see, error handling should be close to the failing code, because that way it reflects the natural flow and logic of the program, and make the code easier to write and easier to understand and maintain.

    Whoever first came up with the idea of exceptions was probably thinking of truly exceptional situations, like a hard disk failure when you call FileOpen(), but now the situation has degenerated to the point where many expected conditions are exceptions now just because exceptions is a hyped, cool, advanced, object-oriented feature.

    In practice, exception doesn’t offer any real benefit over returning error codes, but it has the side-effect of transforming error handling and clean-up codes into a more difficult-to-write and awkward format.

    For example, consider the following code:

    if ( FAILED(A()) ) { CleanUpA(); return; }
    if ( FAILED(B()) ) { CleanUpB(); return; }
    if ( FAILED(C()) ) { CleanUpC(); return; }
    if ( FAILED(D()) ) { CleanUpD(); return; }

    now assume all functions throw exceptions, here’s how you’d write it:

    try {
    step=a; A();
    step=b; B();
    step=c; C();
    step=d; D();
    } catch (…) {
    if (a==step) {CleanUpA(); return; }
    if (b==step) {CleanUpB(); return; }
    if (c==step) {CleanUpC(); return; }
    //if the assert fails, then it’s a real exception
    assert(d==step);
    CleanUpD(); return;
    }

    I’d pick the non-exception code any day..

  9. Frank Hileman says:

    Regarding Shane’s fix: it still would not solve the debugging hell and performance problems from using an exception as the sole means of communication for failure information.

    I know two real-world apps that had performance problems from the misuse of exceptions. One was the C# code generated by ANTLR, a parser generator. When used to parse large files, failure info was transmitted by throwing exceptions internally. Performance was poor, and the design was modified to use a different mechanism (I think a state var on an object).

    The second app is a 2D graphical modeling tool based on GDI+, in C#. Internally GraphicsPath.Widen is called, which can throw an exception when zooming out, with large pen widths. Given that it is difficult to determine when the function will throw the exception (without duplicating its undocumented algorithm), the only feasible solution is to catch the exception, and try something else. Unfortunately the exception throw/catch mechanism is extremely slow: at a certain zoom out point, the screen updates become visibly sluggish. The problem was tracked to the exception catch. There is no workaround, since no non-exception function is provided. In this case the best solution would have been graceful degradation of the Widen function, with no exception thrown. But an error code or state flag in the object would have been better than the exception, at least.

    So every time an API designer decides to provide a single function for an operation, that only communicates errors with exceptions, and the errors may not be "exceptional" in some context (almost always the case), the API user in that context is condemmed to:
    – extra lines of code (for the catch)
    – debugging hell (since the exception is thrown often)
    – potentially, an unsolvable performance penalty

    According to Chris Brumme, "Relative to straight-line local execution, exception performance will keep getting worse. Sure, we might dig into our current behavior and speed it up a little. But the trend will relentlessly make exceptions perform worse."

    Bo: I think you are right that sometimes exceptions are misused because they are "cool". However, when you don’t need to catch the exception locally, and have no cleanup, exceptions can create simpler code. And there is the using statement.

  10. Shane King says:

    I think it could fix the debugging problem somewhat, at least if my understanding of how the exception mechanism works in .NET is correct.

    It would in theory be possible for the debugger to determine if the exception would be caught by a Catch() statement, and not break into the debugger if that is correct.

    I doubt all cases are able to be determined this way, but you’d clean up the majority of them I’d think.

    The performance problems are probably unavoidable in the .NET model. Exceptions are just too heavyweight. So you’re right, it’s probably unavoidable.

    The problem I find with return codes, quite apart from the fact that you can forget to set them, is that it makes it a real pain to code. Instead of returning the value you’re interested in, functions now have to take out params, which is pretty nasty.

    You turn something simple like:

    a.someMethod(b.getFirstParam(), c.getSecondParam());

    into a several line mess:

    int param1;
    if (b.getFirstParam(out param1))
    // handle error

    int param2;
    if (c.getSecondParam(out param2))
    // handle error

    a.someMethod(param1, param2);

    Bleh, it’s just painful to code like that. I’d only consider it for the most performance sensitive parts of an app, since it’s much harder to read and to write.

    It gets even worse when your methods are returning objects that you’d like to chain calls on …

    a.methodA().methodB().methodC();

    becomes an absolute mess without exceptions, which I’m not even going to bother writing.

    It’s really a compromise, error codes are for when you either need absolute best performance, or you need to deal with the error code locally, exceptions are much better when you don’t care to handle the error locally.

    Unfortunately, the library developer may not know which category the client code will fall into. Making two versions of each library method (one that throws, one that gives an error code) seems like a lot of duplication for the library developer.

    And in a catch-22, providing both increases the amount of code and may actually slow the application down. 😉

  11. Frank Hileman says:

    Shane, I prefer exceptions for clean code. But if there is any doubt about it, it is better for the API consumer to be forced to create an exception throwing wrapper function around a non-exception throwing function, than vice-versa. Your idea would help the debugging hell if the debugger was smarter in the future.

    If it is possible for the caller to easily determine that an exception will be thrown, that is no problem. So throwing an exception for a null reference is fine. But when the caller must duplicate the algorithm of the function to determine if the exception will be thrown, and no other alternative function is provided, there is potentially a serious problem. If exceptions are misused we can look forward to a long notorious history of debugging hell and exception-related performance problems in managed applications.

  12. Ken Brubaker says:

    For my own reference, I thought I’d compile a quick list of design guidelines added by Brad Abrams, et al.

  13. Tim Lovell-Smith says:

    One possible good thing about exceptions is that it is then unnecessary to look up the return values in the API, one can just wrap every function call which might fail in a try/catch.

  14. rerdavies says:

    You’ve got to be kidding. I can’t belive that this is even open for discussion anymore. What are you guys? Trolls?

    Exceptions are a major innovation in programming, and there’s no substitute for them.

    Unfortunately, the .net implementation is broken badly on a number of fronts.

    The complaints about poor performance miss the point: the problem isn’t that exception based programming is slow. It’s that exception performance in .net is attrocious, and something needs to be done about it urgently. Exceptions aren’t usable .net because the performance is so bad. It takes a significant fraction of a second to throw and field and exception. C++ exception are lgihtweight and clean, and there’s no problem whatsover with performance (ANTLR/C++ being a great example of a program that throws hundreds of thousands of exceptions a second without any problems).

    Cleanup after exception handling?? Another serious problem with .net. Cleanup in C++ is a non-problem because RIA coding cleans things up with destructors. Cleanup doesn’t have to be done in exception handlers — just recovery. Not so in .net unfortunately, which leaks system resources into the garbage collection pool at the drop of a hat.

    Garbage collection isn’t a solution to this problem. File handles, system resources. All these things need guaranteed RIA cleanup. The .net solution to this (IDispose) is a very poor substitute.

    The problem with using NULL to return error conditions is that NULL just says that the call failed, not why it failed. Consider the case where the open fails because of permissions! I’ll lay good money that anyone who’s using that NULL return call convention doesn’t detect the permissions scenario which *should* have generated an exception.

    Numeric error codes: the problem is that they are poisonous to propogate. How do I return a numeric error code that I receive from a callee module to to a caller? How does my caller translate that error into a user-understanble error message.

    The *big* advantage of exception based programming is that you can return both error code (either a numeric error code, or a class-based coding) as well as sufficient contextual information to formulate a user-intelligible error. Returning error messages along with error codes means that the source of the error can build a sensible error message, and intervening modules can tack contextual information around the original error message. e.g. :

    Unable to open database connection. Permission denied.

    or

    Unable to open database connection. Server XYZ is not reachable.

    In this case, the first and second halves of the message come from different modules. The result is an appropriate and intelligible error message. The outer modules have no need to even know that server XYZ is involved. You can’t do this with numeric exception codes. COM exceptions got halfway there, but not quite.

    Using one error convention for "good" errors, and another for "bad" errors? Not even worth commenting on.

    > One possible good thing about exceptions is that it is then unnecessary to look up the return values in the API, one can just wrap every function call which might fail in a try/catch.

    Every function call? Noo… Assume *every* function may throw an exception. Don’t wrap anyof them unless you have some very specific recovery action that you want to implement — which is a very rare case. Catch exceptions at the top level, reporting the error using e.what(), and carry on. The big advantage of exceptions is that a very deeply nested routine can throw an unexpected exception with the full expectation that a top level handler will receive it and report it, without having to care at all about the seventeen calling modules between here and there.

    Logging? Not a real replacement for meticulous and proper error handling. The guy over the cubicle wall spent three days going through log files to figure out that his application had run out of disk space because of the 4GB of logfiles generated to populate a 4MB database.

    I had a field problem yesterday. The user had received the following error message (roughly rendered):

    Can’t load filter defaults. Error creating the file ‘… filter defaults configuration filename’: Permission denied.

    — an error message generated by a thrice-chained exception.

    Took me two minutes to look at the permissions, and verify that a GPO had added "Network Users: deny all" permissions to the configuration file. Totally unexpected error. Totally useful error message.

  15. 刘未鹏 says:

    错误处理(Error-Handling)这个重要议题从1997年(也许更早)到2004年左右一直是一个被广泛争论的话题,曾在新闻组上、博客上、论坛上引发口水无数(不亚于语言之争),Bjarne Stroustrup、James Gosling、Anders Hejlsberg、Bruce Eckel、Joel Spolsky、Herb Sutter、Andrei Alexandrescu、Brad Abrams、Raymond Chen、David Abrahams…,各路神仙纷纷出动,好不热闹:-)如今争论虽然已经基本结束并结果;只不过结论散落在大量文献当中,且新旧文献陈杂,如果随便翻看其中的几篇乃至几十篇的话都难免管中窥豹。就连Gosling本人写的《The..

  16. tsunami says:

    【转载地址:http://blog.csdn.net/pongba/archive/2007/10/08/1815742.aspx】 引言

    错误处理(Error-Handling)这个重要议…