8

Can anyone explain what exactly Microsoft means on the below linked page when it says:

"Do not catch an exception unless you can handle it and leave the application in a known state."

https://msdn.microsoft.com/en-us/library/ms173160.aspx

user1451111
  • 219
  • 2
  • 5
  • 19
    How much of the sentence do you understand? Do you know what an exception is? Do you know what it means to catch an exception? – JacquesB Aug 30 '16 at 14:56
  • 8
    You don't catch exceptions just because they are thrown, you catch them because you have some specific and useful recovery you want to undertake. This is the beauty of exceptions, you handle them *only* at a place in the code where you can do some useful recovery, which is often far away from where they are generated. Under many circumstances, the code in between doesn't have to deal with them at all. – Erik Eidt Aug 30 '16 at 16:08
  • 1
    Eric Lippert wrote a fantastic article called [Vexing Exceptions](https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/) a few years ago. It's a quick read and lays out a great mindset for approaching your error handling. –  Aug 30 '16 at 18:35

5 Answers5

20
  • It means catching an exception to just write to a log or worse, do nothing at all, leaves the app in an unknown state.
  • Maybe something that shouldn't be null will be, or something that should have been saved wouldn't.
  • You are just sweeping the dirt under the rug.
  • If you are going to catch an exception do it because you can do something about it so the app can continue its execution in a known (and expected) state.
  • Catch an exception when you actually can do something about the problem that caused it or at least leave the app in a consistent state.

Else...

throw it along... someone up the call stack will have to handle it.

If no-one up the calling stack handles it, it'll just bubble up until it reaches the UI an at least the user will know there's something wrong.

But, as everything in this programming world, there are exceptions (no pun intended):

Sometimes in a finally block you try to close a database resource that could or could have not been initialized. In that case there's a probability of calling close() on a null object. In that case silently catching NullPointerException is OK. You cannot handle it anyway. Although, as as @marco-borchert, checking for null would be cleaner.

Another possible exception to the rule is, as @marco-borchert also pointed out, to log the error and re-throw. Maybe you have info in that scope that won't be available up the call stack and it would be interesting to log even if you will not handle the exception at that level.

Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
  • _there's a probability of calling close() on a null object_ What about checking if that object is null before calling a method on it, possibly creating an exception (involving generating a stacktrace) to throw away? – Marco Borchert Aug 30 '16 at 14:57
  • 8
    _catching an exception to just write to a log_ There's also the possiblity of log and rethrow for cases where you have to expect your exception to be caught and ignored (violating the rule in the OP) higher up the call hierarchy outside of your control. – Marco Borchert Aug 30 '16 at 15:02
  • @MarcoBorchert I incorporated your suggestion int the answer. thx. – Tulains Córdova Aug 30 '16 at 15:26
  • 1
    "Sometimes in a finally block you try to close a database resource that could or could have not been initialized." Isn't this what the ['using'](https://msdn.microsoft.com/en-us/library/yh598w02.aspx) clause is for? – JimmyJames Aug 30 '16 at 16:30
  • Also, there may not be a user or UI so you might want to consider generalizing a bit more. – JimmyJames Aug 30 '16 at 16:32
  • @JimmyJames That would be C# specific. – Tulains Córdova Aug 30 '16 at 16:34
  • @TulainsCórdova Granted but the question references the "Exceptions and Exception Handling" section of C# Programming Guide so I kind of thought that was the context here. Java also has keyword-free 'try-with' clause since 1.7. – JimmyJames Aug 30 '16 at 16:39
  • 2
    _...In that case silently catching NullPointerException is OK. You cannot handle it anyway._ In that case, I would argue that the empty catch block _is_ handling the exception. It's just a degenerate case because there isn't any actual work that the handler needs to do. – Solomon Slow Aug 30 '16 at 16:56
  • What does "throw it along" mean? Is that a technical term? – Robert Harvey Aug 30 '16 at 17:22
  • 1
    @RobertHarvey Not a technical term. It's similar to *pass along*, *to relay*. – Tulains Córdova Aug 30 '16 at 17:28
  • 2
    "Just write to a log" I find essential. In early development, it helps me catch the "BoneHeaded" exceptions Eric Lippert refers to. Being human, there always seem to be some of these. After I *think* I've caught them all, the fact that I do have a log may still show me things I did not consider earlier. Whether or not the exception is "recoverable" is beside the point if I did not consider the possibility of it being raised in the first place. By the time the code is ready to be called "Production", logging in many cases is all the handing that remains. – MickeyfAgain_BeforeExitOfSO Aug 30 '16 at 19:45
  • @mickeyf: key word is "just". Catching "just" to write in a log is bad, because then you never know there was a bug. Catching, writing to a log, and then rethrowing, is absolutely useful and please do that. – Mooing Duck Aug 30 '16 at 22:34
  • 1
    @MooingDuck . Of course I know there was a bug, just not "at the instant it occurred". I don't write logs I have no intention of ever looking at. After I've dealt with the 99.99% of things I can imagine, then when that 0.01% edge case happens I still have a stack trace etc I can examine at leisure to help me improve the code. The thing I didn't imagine is also by definition the thing I didn't know how to handle, so the best I could do was to log it when it unexpectedly occurred. – MickeyfAgain_BeforeExitOfSO Aug 31 '16 at 15:11
5

It means not littering your code with exception handlers. It also means don't do this:

try
{
 //Your code here
}
catch (Exception exception)
{

}

Why is this bad? Catching any exception is not desirable because what if the system is giving you a system exception or out of memory exception? Do you really want your application to continue when the system is telling you there is no more memory?!

So, exception handlers should be specific and should be recoverable. You can always have a high level generic application level exception handler to log those cases when something unexpected occurred rather crashing your application. This is done so your application can shut down gracefully. But other than that, keep exception handlers to a minimum and specific to certain scenarios.

Jon Raynor
  • 10,905
  • 29
  • 47
  • I see empty catch blocks all the time here. When I ask the programmer about it, the answer is almost invariably: "I don't want the program to crash". sigh... – MetalMikester Aug 30 '16 at 17:40
  • This is also sometimes referred to as 'Pokemon exception handling' (gotta catch em all). – Jared Smith Aug 30 '16 at 19:46
  • 3
    "Do you really want your application to continue when the system is telling you there is no more memory?" -- actually yes, but only because I was raised in systems programming and therefore have unrealistic ideas about robustness. If an application can (a) abort the operation that was using loads of memory; (b) let the gc reclaim the loads of memory it was using; (c) inform the user that the operation couldn't be completed; (d) survive any OOM-killer or similar that the OS has; then it can continue. – Steve Jessop Aug 30 '16 at 19:51
  • 4
    ... so for example, if I try to open a file that's too big for my text editor to handle in available memory, actually I *don't* want my text editor to abort. I want it to cleanly fail to open that file (or you know, if we're wishing, I want it to manage the text file without loading the whole thing into memory at once, but we can't have everything. Some operations use a lot of memory). – Steve Jessop Aug 30 '16 at 19:54
  • @Steve - Then you would have catch (OutOfMemoryException outOfMemoryException), nothing wrong with that. Maybe my memory example does not fit all use cases. – Jon Raynor Aug 30 '16 at 20:09
  • @JonRaynor: sure, but depending on the app there might be an obvious "receive an instruction, process it, wait for the next instruction" cycle. In which case your "high level generic exception handler" actually needn't exit, it just returns to "awaiting orders" mode. I mostly use Python, so my favourite example is `KeyboardInterrupt`, meaning that the process has received SIGINT and therefore *really should exit*. Fortunately Guido van Rossum is wise, and in Python `except Exception` doesn't catch `KeyboardInterrupt` since not all exceptions inherit from `Exception`. – Steve Jessop Aug 30 '16 at 21:38
  • Whereas if your application is a program that does a specific amount of work (I dunno, converts a file from jpg to bmp) then it's more likely to be an all-or-nothing deal, and any little thing that goes wrong should be fatal unless it's expected and there's a defined way to handle it. – Steve Jessop Aug 30 '16 at 21:43
2

"Astronauts have a saying that, in space, there is no problem so bad you can't make it worse." - Chris Hatfeld

When we catch exceptions, we have taken a very unusual path through the code. Typically function calls enter at the top of the function body, and end at either the end of the body or at a special return statement. When an exception occurs, neither of those two things happen. We instead follow a completely different path through the code using stack unwinding. Invariants may fall apart in this case.

When we do this, it is very possible to have unintended side effects because we skipped past several layers. Consider this faulty bank logic function:

bool transfer(Account accountFrom, Account accountTo, int amount)
{
    try {
        accountFrom.debit(amount); // remove the specified amount from the account
    } catch (NotEnoughFunds ex) {
        return false; // we failed
    }
    accountTo.credit(amount);
    return true;
}

Now this is pathologically bad as far as banking apps can go, but we do see some attempt to catch the exceptions. However, let's say accountTo.credit(amount) raises a NotImplementedException. What happens now? We unwind the stack, and maybe the layer above prints a helpful log message before continuing. This would be where the real problem occurs. The exception that occurred has invalidated an assumption of the banking application: we have debited one account but not credited another. If we "handle" it, then this assumption is no longer valid from here on out, and the programmer likely never accounted for this. The accountFrom may get audited to explain where this money went, or the entire system may come crashing down as the system tries to balance an equation that is now unbalanceable.

If you want to catch an exception, you need to understand that exception well enough to return the system to a state where no invariants have been broken. In the best of all cases, the code you called does this for you, because the author wrote the code with exceptions in mind. However, it is very common for an author to be exception unaware yet use a library under the hood which is exception aware. In this case, you may catch the underlying library's exception, and maintain its invariants, but fail to correct the invariants of the layer inbetween.

If an invariant is violated, there are very few things we can do. In many environments, it is valid to simply exit the application with an error saying "someone did something I didn't think was possible... we're stopping now." In other environments you are expected to cauterize the wound, isolating how much damage the invariant can do, and excising the damage in a way which doesn't break any more invariants. For example, you might design a system around C#'s AppDomain system, where individual components are separated into different AppDomains and are only allowed to communicate via channels you control. You may be able to design the rules of those channels such that when a thread in one domain throws an unhandled exception, you can tear down that entire AppDomain (because its "invariants" are no longer trusted), and then use your control over the communication channels to manage the side effects of that domain being removed in a way which preserves the invariants of the other domains.

Cort Ammon
  • 10,840
  • 3
  • 23
  • 32
1

Although as the quote says the correct way to handle exceptions is to let them pass up--when you are at the top level, for instance if you are creating your own thread and it has a top-level loop, you nearly always want to catch all exceptions so that your thread doesn't break out and die.

You either want to catch all the exceptions inside the loop so it can continue (at least log them!) or you want to catch them outside the loop, log them and halt the thread. This is what allows you to mostly ignore exceptions in other parts of your program--knowing that it will be handled correctly somewhere up the call chain.

This is true for thread loops, for your main system loop (if you have one) and for any event handlers that don't already have predefined exception handling that suits your needs.

Bill K
  • 2,699
  • 18
  • 18
1

When there is an exception that you don't know how to handle, either there is someone else who knows how to handle it or not. And either the exception happens or it doesn't.

If it doesn't happen, catching the exception makes no difference but is a waste of effort. If it happens and nobody knows how to handle it and you catch it, there's no benefit coming from that - the only benefit would come from someone figuring out how to handle it. So again, a waste of effort. And if there is someone who knows how to handle it, catching it doesn't help but might keep someone from handling it properly.

There is no situation where catching an exception that you don't know how to handle would be helpful.

gnasher729
  • 42,090
  • 4
  • 59
  • 119