ChatGPT解决这个技术问题 Extra ChatGPT

Why should I not wrap every block in "try"-"catch"?

I have always been of the belief that if a method can throw an exception then it is reckless not to protect this call with a meaningful try block.

I just posted 'You should ALWAYS wrap calls that can throw in try, catch blocks.' to this question and was told that it was 'remarkably bad advice' - I'd like to understand why.


M
Mitch Wheat

A method should only catch an exception when it can handle it in some sensible way.

Otherwise, pass it on up, in the hope that a method higher up the call stack can make sense of it.

As others have noted, it is good practice to have an unhandled exception handler (with logging) at the highest level of the call stack to ensure that any fatal errors are logged.


It's also worth noting that there are costs (in terms of generated code) to try blocks. There's a good discussion in Scott Meyers's "More Effective C++".
Actually try blocks are free in any modern C compiler, that information is dated Nick. I also disagree about having a top-level exception handler because you lose locality information (the actual place where the instruction failed).
@Blindly: the top exception handler is not there to handle the exception, but in fact to shout out loud that there was an unhandled exception, give its message, and end the program in a graceful way (return 1 instead of a call to terminate). It's more of a safety mechanism. Also, try/catch are more or less free when there isn't any exception. When there is one propagating it does consumes time each times it's thrown and caught, so a chain of try/catch that only rethrow isn't costless.
I disagree you should always crash on an uncaught exception. Modern software design is very compartmentalized, so why should you punish the rest of the application (and more importantly, the user!) just because there was one error? Crashing the the absolutely last thing you want to do, at the very least try to give the user some small window of code that will let them save work even if the rest of the application cannot be accessed.
Kendall: If an exception gets to a top-level handler, your application is by definition in an undefined state. Although in some specific cases there might be value to preserving the user's data (Word's document recovery comes to mind) the program shouldn't overwrite any files or commit to a database.
C
Community

As Mitch and others stated, you shouldn't catch an exception that you do not plan on handling in some way. You should consider how the application is going to systematically handle exceptions when you are designing it. This usually leads to having layers of error handling based on the abstractions - for example, you handle all SQL-related errors in your data access code so that the part of the application that is interacting with domain objects is not exposed to the fact that there is a DB under the hood somewhere.

There are a few related code smells that you definitely want to avoid in addition to the "catch everything everywhere" smell.

"catch, log, rethrow": if you want scoped based logging, then write a class that emits a log statement in its destructor when the stack is unrolling due to an exception (ala std::uncaught_exception()). All that you need to do is declare a logging instance in the scope that you are interested in and, voila, you have logging and no unnecessary try/catch logic. "catch, throw translated": this usually points to an abstraction problem. Unless you are implementing a federated solution where you are translating several specific exceptions into one more generic one, you probably have an unnecessary layer of abstraction... and don't say that "I might need it tomorrow". "catch, cleanup, rethrow": this is one of my pet-peeves. If you see a lot of this, then you should apply Resource Acquisition is Initialization techniques and place the cleanup portion in the destructor of a janitor object instance.

I consider code that is littered with try/catch blocks to be a good target for code review and refactoring. It indicates that either exception handling is not well understood or the code has become an amœba and is in serious need of refactoring.


#1 is new to me. +1 for that. Also, I'd like to note a common exception to #2, which is if you're designing a library often you'll want to translate internal exceptions into something specified by your library interface to reduce coupling (this may be what you mean by "federated solution", but I am not familiar with that term).
#2, where it isn't a code-smell but makes sense, can be enhanced by keeping the old exception as a nested one.
Regarding #1: std::uncaught_exception() tells you that there's an uncaught exception in flight, but AFAIK only a catch() clause lets you determine what that exception actually is. So while you can log the fact that you're exiting a scope due to an uncaught exception, only an enclosing try/catch lets you log any details. Correct?
@Jeremy - you are correct. I usually log the exception details when I handle the exception. Having a trace of the intervening frames is very useful. You generally need to log the thread identifier or some identifying context as well to correlate log lines. I used a Logger class similar to log4j.Logger that include the thread ID in every log line and emitted a warning in the destructor when an exception was active.
D
D.Shawley

Because the next question is "I've caught an exception, what do I do next?" What will you do? If you do nothing - that's error hiding and the program could "just not work" without any chance to find what happened. You need to understand what exactly you will do once you've caught the exception and only catch if you know.


M
Mitch Wheat

You don't need to cover every block with try-catches because a try-catch can still catch unhandled exceptions thrown in functions further down the call stack. So rather than have every function have a try-catch, you can have one at the top level logic of your application. For example, there might be a SaveDocument() top-level routine, which calls many methods which call other methods etc. These sub-methods don't need their own try-catches, because if they throw, it's still caught by SaveDocument()'s catch.

This is nice for three reasons: it's handy because you have one single place to report an error: the SaveDocument() catch block(s). There's no need to repeat this throughout all the sub-methods, and it's what you want anyway: one single place to give the user a useful diagnostic about something that went wrong.

Two, the save is cancelled whenever an exception is thrown. With every sub-method try-catching, if an exception is thrown, you get in to that method's catch block, execution leaves the function, and it carries on through SaveDocument(). If something's already gone wrong you likely want to stop right there.

Three, all your sub-methods can assume every call succeeds. If a call failed, execution will jump to the catch block and the subsequent code is never executed. This can make your code much cleaner. For example, here's with error codes:

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

Here's how that might be written with exceptions:

// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

Now it's much clearer what is happening.

Note exception safe code can be trickier to write in other ways: you don't want to leak any memory if an exception is thrown. Make sure you know about RAII, STL containers, smart pointers, and other objects which free their resources in destructors, since objects are always destructed before exceptions.


Splendid examples. Yup, catch as high as possible, in logical units, such as around some 'transactional' operation like a load/save/etc. Nothing looks worse than code peppered with repetitive, redundant try-catch blocks that attempt to flag up every slightly different permutation of some error with a slightly different message, when in reality they should all end the same: transaction or program failure and exit! If an exception-worthy failure occurs, I wager most users just want to salvage what they can or, at least, be left alone without having to deal with 10 levels of message about it.
Just wanted to say this is one of the best "throw early, catch late" explanations I have ever read: concise and the examples illustrate your points perfectly. Thank you!
C
Community

Herb Sutter wrote about this problem here. For sure worth reading.
A teaser:

"Writing exception-safe code is fundamentally about writing 'try' and 'catch' in the correct places." Discuss. Put bluntly, that statement reflects a fundamental misunderstanding of exception safety. Exceptions are just another form of error reporting, and we certainly know that writing error-safe code is not just about where to check return codes and handle error conditions. Actually, it turns out that exception safety is rarely about writing 'try' and 'catch' -- and the more rarely the better. Also, never forget that exception safety affects a piece of code's design; it is never just an afterthought that can be retrofitted with a few extra catch statements as if for seasoning.


C
Community

As stated in other answers, you should only catch an exception if you can do some sort of sensible error handling for it.

For example, in the question that spawned your question, the questioner asks whether it is safe to ignore exceptions for a lexical_cast from an integer to a string. Such a cast should never fail. If it did fail, something has gone terribly wrong in the program. What could you possibly do to recover in that situation? It's probably best to just let the program die, as it is in a state that can't be trusted. So not handling the exception may be the safest thing to do.


s
starblue

If you always handle exceptions immediately in the caller of a method that can throw an exception, then exceptions become useless, and you'd better use error codes.

The whole point of exceptions is that they need not be handled in every method in the call chain.


D
Donal Fellows

The best advice I've heard is that you should only ever catch exceptions at points where you can sensibly do something about the exceptional condition, and that "catch, log and release" is not a good strategy (if occasionally unavoidable in libraries).


@KeithB: I'd consider it a second-best strategy. It's better if you can get the log written in another way.
@KeithB: It's a "better than nothing in a library" strategy. "Catch, log, deal with it properly" is better where possible. (Yeah, I know it's not always possible.)
u
user2502917

I was given the "opportunity" to salvage several projects and executives replaced the entire dev team because the app had too many errors and the users were tired of the problems and run-around. These code bases all had centralized error handling at the app level like the top voted answer describes. If that answer is the best practice why didn't it work and allow the previous dev team to resolve issues? Perhaps sometimes it doesn't work? The answers above don't mention how long devs spend fixing single issues. If time to resolve issues is the key metric, instrumenting code with try..catch blocks is a better practice.

How did my team fix the problems without significantly changing the UI? Simple, every method was instrumented with try..catch blocked and everything was logged at the point of failure with the method name, method parameters values concatenated into a string passed in along with the error message, the error message, app name, date, and version. With this information developers can run analytics on the errors to identify the exception that occurs the most! Or the namespace with the highest number of errors. It can also validate that an error that occurs in a module is properly handled and not caused by multiple reasons.

Another pro benefit of this is developers can set one break-point in the error logging method and with one break-point and a single click of the "step out" debug button, they are in the method that failed with full access to the actual objects at the point of failure, conveniently available in the immediate window. It makes it very easy to debug and allows dragging execution back to the start of the method to duplicate the problem to find the exact line. Does centralized exception handling allow a developer to replicate an exception in 30 seconds? No.

The statement "A method should only catch an exception when it can handle it in some sensible way." This implies that developers can predict or will encounter every error that can happen prior to release. If this were true a top level, app exception handler wouldn't be needed and there would be no market for Elastic Search and logstash.

This approach also lets devs find and fix intermittent issues in production! Would you like to debug without a debugger in production? Or would you rather take calls and get emails from upset users? This allows you to fix issues before anyone else knows and without having to email, IM, or Slack with support as everything needed to fix the issue is right there. 95% of issues never need to be reproduced.

To work properly it needs to be combined with centralized logging that can capture the namespace/module, class name, method, inputs, and error message and store in a database so it can be aggregated to highlight which method fails the most so it can be fixed first.

Sometimes developers choose to throw exceptions up the stack from a catch block but this approach is 100 times slower than normal code that doesn't throw. Catch and release with logging is preferred.

This technique was used to quickly stabilize an app that failed every hour for most users in a Fortune 500 company developed by 12 Devs over 2 years. Using this 3000 different exceptions were identified, fixed, tested, and deployed in 4 months. This averages out to a fix every 15 minutes on average for 4 months.

I agree that it is not fun to type in everything needed to instrument the code and I prefer to not look at the repetitive code, but adding 4 lines of code to each method is worth it in the long run.


Wrapping every block seems like overkill. It quickly makes your code bloated and painful to read. Logging a stacktrace from an exception at higher levels shows you where the issue happened and that combined with the error itself generally is enough information to go on. I would be curious of where you found that not sufficient. Just so I can gain someone else's experience.
"Exceptions are 100 to 1000 times slower than normal code and should never be rethrown" - that statement is not true on most modern compilers and hardware.
It seems like overkill and requires a bit of typing but is the only way to perform analytics on exceptions to find and fix the biggest errors first including intermittent errors in production. The catch block handles specific errors if required and has a single line of code that logs.
No, exceptions are very slow. The alternative is return codes, objects, or variables. See this stack overflow post... "exceptions are at least 30,000 times slower than return codes" stackoverflow.com/questions/891217/…
B
Bananeweizen

I agree with the basic direction of your question to handle as many exceptions as possible at the lowest level.

Some of the existing answer go like "You don't need to handle the exception. Someone else will do it up the stack." To my experience that is a bad excuse to not think about exception handling at the currently developed piece of code, making the exception handling the problem of someone else or later.

That problem grows dramatically in distributed development, where you may need to call a method implemented by a co-worker. And then you have to inspect a nested chain of method calls to find out why he/she is throwing some exception at you, which could have been handled much easier at the deepest nested method.


M
Mike Bailey

The advice my computer science professor gave me once was: "Use Try and Catch blocks only when it's not possible to handle the error using standard means."

As an example, he told us that if a program ran into some serious issue in a place where it's not possible to do something like:

int f()
{
    // Do stuff

    if (condition == false)
        return -1;
    return 0;
}

int condition = f();

if (f != 0)
{
    // handle error
}

Then you should be using try, catch blocks. While you can use exceptions to handle this, it's generally not recommended because exceptions are expensive performance wise.


That is one strategy, but many people recommend never returning error codes or failure/success statuses from functions, using exceptions instead. Exception-based error handling is often easier to read than error-code-based code. (See AshleysBrain's answer to this question for an example.) Also, always remember that many computer-science professors have very little experience writing real code.
-1 @Sagelika Your answer consists in avoiding exception, so no need of try-catch.
@Kristopher: Other big disadvantages to the return code is that it's real easy to forget to check a return code, and just after the call is not necessarily the best place to handle the problem.
ehh, it depends, but in many cases (setting aside people who throw when they really shouldn't), exceptions are superior to return codes for so many reasons. in most cases, the idea that exceptions are detrimental to performance is a big ol' [citation needed]
b
bluedog

If you want to test the outcome of every function, use return codes.

The purpose of Exceptions is so that you can test outcomes LESS often. The idea is to separate exceptional (unusual, rarer) conditions out of your more ordinary code. This keeps the ordinary code cleaner and simpler - but still able to handle those exceptional conditions.

In well-designed code deeper functions might throw and higher functions might catch. But the key is that many functions "in between" will be free from the burden of handling exceptional conditions at all. They only have to be "exception safe", which does not mean they must catch.


G
GPMueller

I would like to add to this discussion that, since C++11, it does make a lot of sense, as long as every catch block rethrows the exception up until the point it can/should be handled. This way a backtrace can be generated. I therefore believe the previous opinions are in part outdated.

Use std::nested_exception and std::throw_with_nested

It is described on StackOverflow here and here how to achieve this.

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace! You may also take a look at my MWE on GitHub, where a backtrace would look something like this:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

u
user875234

I feel compelled to add another answer although Mike Wheat's answer sums up the main points pretty well. I think of it like this. When you have methods that do multiple things you are multiplying the complexity, not adding it.

In other words, a method that is wrapped in a try catch has two possible outcomes. You have the non-exception outcome and the exception outcome. When you're dealing with a lot of methods this exponentially blows up beyond comprehension.

Exponentially because if each method branches in two different ways then every time you call another method you're squaring the previous number of potential outcomes. By the time you've called five methods you are up to 256 possible outcomes at a minimum. Compare this to not doing a try/catch in every single method and you only have one path to follow.

That's basically how I look at it. You might be tempted to argue that any type of branching does the same thing but try/catches are a special case because the state of the application basically becomes undefined.

So in short, try/catches make the code a lot harder to comprehend.


z
zhaorufei

Besides the above advice, personally I use some try+catch+throw; for the following reason:

At boundary of different coder, I use try + catch + throw in the code written by myself, before the exception being thrown to the caller which is written by others, this gives me a chance to know some error condition occured in my code, and this place is much closer to the code which initially throw the exception, the closer, the easier to find the reason. At the boundary of modules, although different module may be written my same person. Learning + Debug purpose, in this case I use catch(...) in C++ and catch(Exception ex) in C#, for C++, the standard library does not throw too many exception, so this case is rare in C++. But common place in C#, C# has a huge library and an mature exception hierarchy, the C# library code throw tons of exception, in theory I(and you) should know every exceptions from the function you called, and know the reason/case why these exception being thrown, and know how to handle them(pass by or catch and handle it in-place)gracefully. Unfortunately in reality it's very hard to know everything about the potential exceptions before I write one line of code. So I catch all and let my code speak aloud by logging(in product environment)/assert dialog(in development environment) when any exception really occurs. By this way I add exception handling code progressively. I know it conflit with good advice but in reality it works for me and I don't know any better way for this problem.


A
Amit Kumawat

You have no need to cover up every part of your code inside try-catch. The main use of the try-catch block is to error handling and got bugs/exceptions in your program. Some usage of try-catch -

You can use this block where you want to handle an exception or simply you can say that the block of written code may throw an exception. If you want to dispose your objects immediately after their use, You can use try-catch block.


"If you want to dispose your objects immediately after their use, You can use try-catch block." Did you intend this to promote RAII/minimal object lifetime? If so, well, try/catch is completelly separate/orthogonal from that. If you want to dispose objects in a smaller scope, you can just open a new { Block likeThis; /* <- that object is destroyed here -> */ } - no need to wrap this in try/catch unless you actually need to catch anything, of course.
#2 - Disposing objects (which were manually created) in the exception seems weird to me, this can be useful in some languages no doubt, but generally you do it in a try/finally "within the try/except block", and not specifically in the except block itself - since the object itself may have been the cause of the exception in the first place, and thus cause another exception and potentially a crash.