0

I came across this question and super confused about rethrowing concept in the following example:

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}

Above after error logging, the same exception is thrown again. I don't understand this. I summarized my questions in two parts:

1-) If we already log the error why would we rethrow the same exception? Can you give an example scenario?(Is it done to inform client? Wouldn't that cause bad user experience). An example would clarify.

2-) Why don't we handle the exception right after logging it inside the catch instead of rethrowing it? Does the code inside the catch break after logging? (I really don't get the point why we throw the same thing when we are inside it already)

1
  • There is no single reason why. It all depends on the use case. Mar 29, 2021 at 23:36

3 Answers 3

7

The key point you're missing is that the same exception can be caught in completely different parts of the code, which have different things they can do with it. Some code might want to log the error in a particular format, but not actually be able to recover from the error; code elsewhere in the application then needs to know that the error happened, and decide what to do about it.

For example, imagine you have some database abstraction code, that takes an SQL query and its parameters and passes it to an underlying database driver. You might have this:

  1. The "add comment" function in the application needs to run some SQL, so calls the abstraction function.
  2. The abstraction function calls the database driver.
  3. The database driver throws an exception.
  4. The abstraction code catches the exception, and logs it to a "failed query" log. This makes sure that every database error is guaranteed to end up in the log, and the abstraction might have extra details in its local scope that can be included in the log. The error hasn't been handled, though, so it re-throws the exception.
  5. The "add comment" function catches the exception again, and displays a message to the user that their comment couldn't be added.
2
  • Why not handling it after the logging line instead of rethrowing? We can write the handing code right after logging line inside the catch? That was what i was confused. Does the catch stop working after logging?
    – pnatk
    Mar 30, 2021 at 19:06
  • @pnatk At step 4, we're in generic database abstraction code. It has absolutely no idea how to display a message to the user, much less what the user was trying to do. So the exception needs to propagate upwards to code that does. At step 5, we're in high-level UI code; we don't want to put the logging code there, because it would be copy-and-pasted logic that would be easily forgotten when we added new UI actions. Having two separate catch blocks lets each one do something relevant to its part of the application, and they don't need to know about each other.
    – IMSoP
    Mar 30, 2021 at 19:19
3

Is it done to inform client?

Precisely that.

Wouldn't that cause bad user experience?

"Client" is not synonymous with "end user". The only exceptions that reach the end user are called unhandled exceptions. Exceptions bubble up the call stack, but they should be handled (at least once) at some point, and they should not bubble up to the end user.

I really don't get the point why we throw the same thing when we are inside it already

Because it's not just a matter of whether it's handled or not, but also who handles it.

In complex codebases, one operation (e.g. "buy this product") can be orchestrated as a sequence of multiple operations (e.g. confirm availability in stock, make order, process payment, packaging, schedule delivery). In a properly loosely coupled codebase, those suboperations have no direct knowledge of the "parent" operation that they belong to. Therefore, when the suboperation handles the exception, it's not able to account for how the parent operation wants it to be handled.

So instead, the suboperation lets the exception bubble further, and then it's the parent operation's job to catch the exception again and handle it the way it wants to.

For example, the credit card payment suboperation may just report the error to the payment platform provider, whereas the "purchase product" parent operation will then decide to send the customer an invoice instead since the direct payment didn't work.


It really helps to think of this as a company with humans. Just like your layer communicate with one another, the employees of a company communicate with one another. So let's use an example.

Manager has instructed Driver to deliver Parcel to Customer, and provided him with the key to a specific Truck (truckId). As it turns out, there is no Truck in the parking lot which fits that key. That is the equivalent of the exception being thrown.

As is customary, the Driver makes an official report of this to the police. This is the equivalent of writing to the log.

Okay, the report has been logged. Is that the end of it? Because don't forget that the Manager is still in the office, thinking that the Customer will receive their Parcel.
Should the Driver still go back to the Manager to explain that he couldn't fulfill his task?

This is where you have options:

  • Maybe the driver simply explains precisely what happened (= rethrow the same exception)
  • Maybe the driver isn't allowed to explain internal logistics, so they simply explain that the delivery can't be done (= throw a new exception)
  • Maybe the manager doesn't quite understand internal logistics, so they simply explain that the delivery can't be done and if the manager asks about it they can explain precisely what happened (= throw an exception, with the original exception as inner exception)

But what you're suggesting, i.e. not even continuing throwing an exception because it has already been handled, is more akin to:

  • The driver doesn't tell the manager that the parcel was never delivered.

That's not to say that it is inherently wrong to do so, but it is very contextual whether it is wrong or not, and it's more often correct to not swallow the exception.


Now, as is the case for most analogies, this is of course oversimplified. But it highlights the main purpose: alerting the consumer (i.e. those higher ups on the call stack) that the expected outcome has not been achieved, so that those higher ups are then able to respond to this issue however they choose.

The reason you need to let it bubble up is because the "lower" handler might not have all the necessary contextual information that the "higher up" handler does.

0

Also remember that "exception" is not synonymous with "error!" While runtime errors throw exceptions, you can deliberately throw user-defined exceptions of your own devising. An "exception" is simply: "an exception to the normal flow of control," for whatever reason.

For instance, an order-processing system might, deep within many layers of logic, decide that "the order must be cancelled." This is an "exceptional situation," and we don't want to muddy-up the logic checking for it. Instead, it throws an exception. Maybe several of the nested layers of logic catch the exception, do something, and (maybe ...) re-throw it. In this way, each of them will reliably execute some bit of code to handle this "exception to the rule," but only when it actually happens. (Some outer-level handler eventually catches the exception and maybe "eats" it so that it doesn't emerge as "unhandled" and kill the program.)

Notice how the "exceptional situation" path is clearly separated from "typical logic." And, equally importantly, how "typical logic" no longer has to test for(!) "exceptional occurrences." (And maybe, "whoopsie!", miss one.)

The use of an exception keeps the many layers of logic from having to individually test for this possibility, and maybe "doing so incorrectly." For instance, in the bad old days you had to test the return-code from, say, fopen() to discover if the file was really opened. Much cleaner to use an exception: if the function returns, then the file is open. If the file could not be opened, you wind up in the exception handler instead – perhaps having just "blown your way right out of" many layers of function-nesting, but in a completely-controlled and predictable way.

Exceptions are represented by software objects which are passed to the handler. They can be user-defined and they can also contain a payload of information which provides detail about the occurrence, which is made available to each handler.

5
  • While it does happen, using exceptions to model complex execution flows like this is somewhat controversial. I think many people would say that exceptions and events are very different things, and should not be confused.
    – IMSoP
    Mar 30, 2021 at 13:54
  • Thank you. "Good catch." To eliminate the ambiguity, I replaced the word "event" with "occurrence." In our parlance, the word "event" has a very specific meaning which I very-specifically did not intend to refer to. "The two concepts are entirely unrelated," and this must be made perfectly clear. Mar 30, 2021 at 19:19
  • To clarify: an "event" is a synchronous action which the logic expects, and which in fact is fundamental to its operation. Whereas, an "exception" is an alternative flow-of-control which is to be followed only under ... well ... "exceptional" circumstances. ("It's not supposed to happen, but it just did.") Mar 30, 2021 at 19:29
  • I think your example blurs that boundary more than some people would be comfortable with. "The order must be cancelled" is a decision within the business domain of the application, which will have specific well-defined consequences. It may be a rare path for the program to follow, but it's not necessarily exceptional. I think some people would bluntly disagree with your first sentence, and say that exceptions are an error-handling mechanism, and any other use is misuse.
    – IMSoP
    Mar 30, 2021 at 19:33
  • I entirely agree. I simply grabbed "the first example that popped into my head." Probably, it was not a good choice. "In fact, no it wasn't." In the real world, a programmer probably would not have chosen to use an exception in this case. But, I'm too lazy to re-write my post. 😉 A user-defined exception is used to reflect "an exceptional situation" that rarely(?) happens, but which can nonetheless be anticipated. Exceptions "blast out of the subroutine-call stack, but always wind up at an agreed-upon, previously-saved state." And I guess that's the real reason why we have them. Mar 30, 2021 at 19:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.