What are the principles guiding your exception han

2019-01-13 08:31发布

问题:

There is a lot of relativity involved in working with exceptions. Beyond low level APIs where exceptions cover errors raised from hardware and the OS there is a shady area where the programmer decides what constitutes an exception and what is a normal condition.

How do you decide when to use exceptions? Do you have a consistent policy regarding exceptions?

回答1:

Exceptions should not be used as a method of passing information internally between methods inside your object, locally you should use error codes and defensive programming.

Exceptions are designed to pass control from a point where an error is detected to a place (higher up the stack) where the error can be handled, presumably because the local code does not have enough context to correct the problem and something higher up the stack will have more context and thus be able to better organize a recovery.

When considering exceptions (in C++ at least) you should consider the exception guarantees that your API makes. The minimum level of guarantee should be the Basic guarantee though you should strive (where appropriate) to provide the strong guarantee. In cases where you use no external dependencies from a articular API you may even try to provide the no throw guarantee.

N.B.Do not confuse exception guarantees with exception specifications.

Exception Guarantees:

No Guarantee:

There is no guarantee about the state of the object after an exception escapes a method In these situations the object should no longer be used.

Basic Guarantee:

In nearly all situations this should be the minimum guarantee a method provides. This guarantees the object's state is well defined and can still be consistently used.

Strong Guarantee: (aka Transactional Guarantee)

This guarantees that the method will completely successfully Or an Exception will be thrown and the objects state will not change.

No Throw Guarantee:

The method guarantees that no exceptions are allowed to propagate out of the method. All destructors should make this guarantee.
| N.B. If an exception escapes a destructor while an exception is already propagating
| the application will terminate



回答2:

This blog entry from Eric Lippert, a Senior Software Design Engineer at Microsoft, sums up an excellent and brief set of exception strategy guidelines.

In short:

  • Fatal: Terrible errors that indicate your process is totally unrecoverable. Clean up whatever resources you can, but don't catch them. If you're writing code that has the ability to detect such a situation, by all means, throw. Example : Out of memory exception.

  • Boneheaded: Relatively simple errors that indicate your process can't operate on whatever data it's being handed, but would continue on normally if whatever situation caused the error is simply ignored. These are better known as bugs. Don't throw or catch them, but instead prevent them from happening, usually by passing errors or other meaningful indicators of failure that can be handled by your methods. Example: Null argument exception.

  • Vexing: Relatively simple errors that code you don't own is throwing at you. You must catch all of these and deal with them, usually in the same way as you would deal with a Boneheaded exception of your own. Please don't throw them right back out again. Example: Format exception from C#'s Int32.Parse() method

  • Exogenous: Relatively straightforward errors that look a lot like Vexing (from other people's code) or even Boneheaded (from your code) situations, but must be thrown because reality dictates that the code that's throwing them really has no idea how to recover, but the caller probably will. Go ahead and throw these, but when your code receives them from elsewhere, catch them and deal with them. Example: File not found exception.

Of the four, the exogenous ones are the ones that you have to think about most to get right. An exception indicating a file is not found is appropriate to throw for an IO library method, in that the method almost certainly will not know what to do should the file not be found, especially given that the situation can occur at any time and that there is no way to detect whether or not the situation is transient. Throwing such an exception would not be appropriate for application-level code, though, because that application can get information from the user on how to proceed.



回答3:

  1. Never throw exceptions from destructors.

  2. Maintain some basic level of exception guarantees about the state of the object.

  3. Do not use exceptions to communicate errors which can be done using an error code unless it is a truly exception error and you might want the upper layers to know about it.

  4. Do not throw exceptions if you can help it. It slows everything down.

  5. Do not just catch(...) and do nothing. Catch exceptions you know about or specific exceptions. At the very least log what happened.

  6. When in the exception world use RAII because nothing is safe anymore.

  7. Shipping code should not have suppressed exceptions at least with regards to memory.

  8. When throwing exceptions package as much information as is possible along with it so that the upper layers have enough information to debug them.

  9. Know about flags that can cause libraries like STL to throw exceptions instead of exhibiting unknown behaviour (e.g. invalid iterators / vector subscript overflow).

  10. Catch references instead of copies of the exception object?

  11. Take special care about reference counted objects like COM and warp them in reference counted pointers when working with code that could throw exceptions.

  12. If a code throws an exception more than 2% of the time consider making it an error code for performance's sake.

  13. Consider not throwing exceptions from undecorated dll exports / C interfaces because some compilers optimize by assuming C code to not throw exceptions.

  14. If all that you do for handling exceptions is something akin to below, then don't use exception handling at all. You do not need it.

    main 
    {
         try {
        all code....
        }
        catch(...) {} 
    }
    


回答4:

Exceptions are expensive in processing time, so they should only be thrown when something happens that really shouldn't happen in your app.

Sometimes you can predict what kind of things might happen and code to recover from them, in which case it is appropriate to throw and catch an exception, log and recover, then continue. Otherwise they should just be used to handle the unexpected and exit gracefully, while capturing as much information as possible to help with debugging.

I'm a .NET developer, and for catch and throw, my approach is:

  1. Only try/catch in public methods (in general; obviously if you are trapping for a specific error you would check for it there)
  2. Only log in the UI layer right before suppressing the error and redirecting to an error page/form.


回答5:

The context this answer is given in is the Java language.

For normal errors that may pop up, we handle those directly (such as returning immediately if something is null, empty, etc). We only use an actual exception for exceptional situations.

However, we do not throw checked exceptions, ever. We subclass RuntimeException for our own specific exceptions, catching them where applicable directly, and as for exceptions that are thrown by other libraries, the JDK API, etc, we do try/catch internally and either log the exception (if something happened that really shouldn't have and you have no way of recovering like a file not found exception for a batch job) or we wrap the exception in a RuntimeException and then throw it. On the outside of the code, we rely on an exception handler to eventually catch that RuntimeException, be that the JVM or the web container.

The reason this is done is that it avoids creating forced try/catch blocks everywhere where you might have four instances of calling a method but only one can actually handle the exception. This seems to be the rule, not the (no pun intended...ouch) exception, so if that fourth one can handle it, it can still catch it and examine the root cause of the exception to get the actual exception that occurred (without worrying about the RuntimeException wrapper).



回答6:

I think there's usually a good way to determine exceptions based on access to resources, integrity of data and the validity of data.

Access Exceptions

  • Creating or connecting to any kind of connection (remote, local).
    • Occurs in: Databases, Remoting
    • Reasons: Non-existent, Already in Use or Unavailable, Insufficient/Invalid Credentials
  • Opening, Reading or Writing to any kind of resource
    • Occurs in: File I/O, Database
    • Reasons: Locked, Unavailable, Insufficient/Invalid credentials

Integrity of Data

  • There could be many cases where the integrity of the data matters
    • What it references, what it contains...
    • Look for resources on the methods or code that requires a set of criteria for the data to be clean and in a valid format.
    • Example: Trying to parse a string with the value 'bleh' to a number.

Validity of Data

  • Is this the correct data provided? (It's in the right format, but it might not be the correct set of parameters for a given situation)
    • Occurs in: Database Queries, Transactions, Web Services
    • Example: Submitting a row to a database and violating a constraint

Obviously there are other cases, but these are usually the ones I try to abide by where its needed.



回答7:

I believe that the best way to use exceptions depends on which computer language you are using. For instance Java has a much more solid implementation of exceptions than C++.

If you are using C++, I recommend that you at least try to read what Bjarne Stroustrup (the inventor of C++) has to say about exception safety. Refer to appendix E of his book "The C++ programming language".

He spends 34 pages trying to explain how to work with exceptions in a safe way. If you do understand his advice then that should be all that you need to know.



回答8:

Others may have to correct/clarify this, but there's a strategy called (I believe) "contract-driven development", where you explicitly document in your public interface what the expected preconditions are for each method, and the guaranteed post-conditions. Then, when implementing the method, any error which prevents you from meeting the post-conditions in the contract should result in a thrown exception. Failure to meet preconditions is considered a program error and should cause the program to abort.

I'm not sure if contract-driven development speaks to the question of catching exceptions, but in general you should only catch exceptions that you expect and can reasonably recover from. For instance, most code cannot meaningfully recover from an Out Of Memory exception, so there is no point in catching it. On the other hand, if you are trying to open a file for writing, you can (and should) handle the case that the file is exclusively locked by another process, or the case that the file has been deleted (even if you checked its existence before trying to open it).

As noted by another commenter, you should also avoid using exceptions to handle expected conditions that can be expected and avoided. For instance, in the .NET framework, int.TryParse is preferable to int.Parse with a try/catch, especially when used in a loop or such.



回答9:

this article from bea(now oracle) is a good exposition on how to go about it : http://www.oracle.com/technology/pub/articles/dev2arch/2006/11/effective-exceptions.html. It kinda assumes Java but you should be able to use it for other environments as well.



回答10:

As a C++ developer, my own policy is not to throw exceptions from what I consider to be public apis to my classes/modules (in fact, a requirement with COM). However, I use exceptions extensively in private class implementation. For example, working with ATL:

HRESULT Foo()
{
    HRESULT hr = S_OK;
    try {
        // Avoid a whole lot of nested ifs and return code
        // checking - internal stuff just throws.
        DoStuff();
        DoMoreStuff(); // etc.
    } catch ( CAtlException& e ) {
        hr = e;
    }
    return hr;
}

void DoSomething()
{
    // If something goes wrong, AtlThrow( E_FAILED or E_WHATEVER ); 
}


回答11:

My policy on exception handling can be found at:

http://henko.net/imperfection/exception-handling-policy-throwing-exception/.

(Hope it's not against the rules to promote a web site, but it is a bit too much information to paste here.)



回答12:

Aren't exceptions raised by the language environment in accordance with the spec. of the language being used if indeed it does have the concept of exceptions? I'm thinking of "divide by zero" in Java, or CONSTRAINT_ERROR in Ada vs. nothing at all in C.

How can a programmer "decide" to use exceptions after selecting a programming language that has exceptions defined within its makeup?

Edit: Or rather than "using" exceptions, do you mean when to have a cohesive and consistent policy about "handling" exceptions?

Edit2: You might like to check out the free chapter from Steven Dewhurst's book "C++ Gotchas", specifically Gotcha 64 and Gotcha 65. Though it is focused on C++, the lessons involved are useful in other languages.