Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does "Only catch exceptions you can handle" really mean?

I'm tasked with writing an Exception Handling Strategy and Guidelines document for a .NET/C# project I'm working on. I'm having a tough go at it. There's plenty of information available for how/when to throw, catch, wrap exceptions, but I'm looking for describing what sorts of things should go on inside the catch block short of wrapping and throwing the exception.

try
{
   DoSomethingNotNice();
}
catch (ExceptionICanHandle ex)
{
   //Looking for examples of what people are doing in catch blocks
   //other than throw or wrapping the exception, and throwing.
}

Thanks in advance

like image 448
KyleLib Avatar asked Jan 12 '11 20:01

KyleLib


5 Answers

It means exactly that. If you are expecting code you're running to throw an exception, and when that exception is thrown your code knows what went wrong and how to proceed, then catch the exception and handle it.

Basically, the rule exists to prevent anti-patterns like:

try
{
   ...
}
catch(Exception ex)
{
   throw;
}

The catch here does nothing but add a speed bump to unwinding the call stack. If you don't actually want to do anything with the exception you're catching, you shouldn't even bother with the catch.

A related but far more valid case is where you don't care about the exception being thrown, but you need to clean up in all cases. In that case, skip the catch; you don't need it, just make it a try-finally block.

EDIT: To answer the question in the post, not just the subject, you could write a rule as follows: "Do not code a try-catch statement that does not do anything, or only rethrows the caught exception. All catch statements should perform some value-added action relating to the thrown exception."

For example, let's say you are trying to connect to a SQL Server instance using credentials supplied by the user when they log into your app. Dozens of things could go wrong, some of which you can't expect, some of which you should.

  • Server isn't responding - you can try again; perhaps call the connection method recursively in the catch, with a "retry counter" to break the otherwise infinite loop.
  • User failed authentication - show a friendly (or not-so-friendly, but concise and understandable) message in red on the dialog box.
  • User not authorized to connect to the specified DB - Depends on your security setup; in most offices, that's something you should e-mail the DBA about because it means he created the login but forgot to assign the proper rights.
  • Network not available: You can alert the user through an error on the login dialog or a new dialog, retry a couple of times, etc.
  • Division by zero - WTF? What could possibly cause a Div by Zero during a login? You're not expecting this exception, you have no clue what went wrong in this case and therefore can't continue running code, so don't catch it.
  • If anything goes wrong, you may want to log the message to a file or a shared resource for audit/security purposes. This should happen at lower levels if you want to continue execution, or higher levels if you're going to gracefully shut down afterward.

All of these examples involve first catching the exception of a known type and interrogating it to see what exactly went wrong, then performing some known action that can allow the program to continue execution. The object is to prevent the application from crashing and burning when something goes wrong that you know could go wrong, but know how to keep the program running in that case.

The basic rules for catching exceptions:

  1. If you aren't expecting an exception, don't catch one.
  2. If you can't or don't want to continue execution of code after receiving an exception, whether you know it can happen or not, don't catch it.
  3. If you are expecting the exception to occur, and know-how to continue executing code when it happens (at least for a while), then catch and perform any special actions you need in order to do so.
  4. NEVER trap exceptions (an empty catch block); that causes applications to fail silently in even more unpredictable ways.
  5. NEVER leave catch-and-rethrow (a catch block with only a rethrow) in production code. They can sometimes be useful when debugging as they allow you to identify specific segments of code that are failing, but in production code, they're just a speed bump to throwing out or actually dealing with the exception.
like image 125
KeithS Avatar answered Nov 16 '22 01:11

KeithS


I think the basic idea underlying this common piece of advice is to avoid scenarios like this:

try
{
    SomeImportantResource = GetSomeImportantResource();
    SomeOtherImportantResource = GetSomeOtherImportantResource();
}
catch (Exception ex)
{
    SomeGlobalErrorHandlingMechanism(ex);
}

I've worked with developers who, when confronted with a bug, would simply wrap the offending code in a try/catch block and say, "I fixed the bug." The problem in scenarios like the above example is that by simply catching an exception and not fixing the problem that caused it, you're liable to undermine the solidity of your program. Above, what the catch has done is made us uncertain whether SomeImportantResource and SomeOtherImportantResource were ever initialized properly. It seems likely that there could be code elsewhere in the program that requires for these to be initialized, in which case, we've just introduced a bug by "fixing" a bug.

So I think the standard wisdom is to only try to deal with an exception if you can recover from it in such a way that it does not compromise any other code elsewhere in your program.

Or, better than that: don't catch the exception and make some feeble attempt (or non-attempt) to "handle" it; figure out what caused it and fix that problem. Obviously this is not always possible, but it is possible a lot more often than it should be.

like image 20
Dan Tao Avatar answered Nov 16 '22 00:11

Dan Tao


Consider if you had an application like OneNote that lets you store your files on a shared network drive, but in the event the network is unavailable, then it uses local storage temporarily until the main storage is available.

If your program got an exception while interacting with the files, then you could retry the action with the local storage.

This is an example where you have a specific program behavior you want, and accomplish it by how you handle the exception. Generally, you should try to find a way to accomplish your goal without using exception handling, such as in the above exmple, you could always check to see if the file is available before attempting to operate on it. That way you can just code it as an "if/else" instead of a "try/catch". However, if you did that, there is still always the chance in the above case that someone may lose access to a file in the middle of an operation, such that regardless of whether you checked in advance, you still might get an exception that you can handle gracefully. So you'd probably refactor your else block into a function that is both called from the else and the catch, so that you can gracefully fallback to local storage in either case.

I also often include logging if there is no security issue with what I'm logging, and a rethrow as you mentioned, and my logging includes more descriptive information and context information, maybe some local values, which make debugging easier. I always strive to have log files so detailed that I can determine the cause of a problem without having to reproduce on my machine. I hate hearing programmers make the "I can't reproduce it" excuse. You don't have to reproduce it. If your logging is adequate then there is no need to reproduce it.

When an exception trickles up via rethrow's all the way to your GUI layer, then at that point is where you catch it and do not rethrow it, but instead display a message to the user indicating that an unexpected error occurred, and usually exit the application. You might give them an opportunity to save work, but maybe automatically making a backup of the file being overwritten, as an unhandled exception is something you never coded for, meaning something might be corrupt, and you might be saving a bad file, yet leading the user to believe they are saving their work. This is ultimately the reason many program opt to kill themselves if something unexpected occurs, as from that point on who knows what state the program might be in, and something as simple as saving some rows in a database might have serious consequences and hose alot of data.

like image 29
AaronLS Avatar answered Nov 15 '22 23:11

AaronLS


If you can perform an action when you catch an exception that is helpful in some way (such as executing a block of code that will perform the function attempted in the try statement, but does it in a different, but perhaps less efficient way, or simply informing the user that their action couldn't be performed), then you should catch it and do so. If you are simply logging the exception to track down the problem later, then you should rethrow the exception throw; (NOT throw ex;), in case there is another block of code that can handle that type of exception.

It's also acceptable to catch an exception to wrap the caught exception in your own exception that may make more sense to the calling function.

like image 33
Brian Ball Avatar answered Nov 16 '22 01:11

Brian Ball


Some examples:

  1. Log the exception and just carry on
  2. Retry the thing that went wrong
  3. Try another method of doing what you were trying to do

It all depends on what went wrong. The point is, just catching and re-throwing is of no use to anyone.

like image 40
OrangeDog Avatar answered Nov 16 '22 00:11

OrangeDog