Most programmers don't really like to think about exception handling that much. So to get you in the mood, click through to this article (Exception Handling in Java and C#). Don't be put off by the title; instead, just go to the fourth paragraph for a particularly lurid story about how a minor error in exception handling caused the rather expensive loss of a rocket soon after its launch. There! Now don't you feel better that usually, at most, your exceptions will simply require a restart of your program?
Now I must admit, I certainly write my share of code that "blows up," and I constantly look for ways to improve how I handle exceptions. The goal of developing self-healing code seems to be elusive, so I'm becoming more willing to settle for "gets as much information as possible and exits gracefully, if able." The idea of getting as much information as possible is easy to understand. I routinely set my IBM i job logging to LOG(4 00 *SECLVL); I would rather have way too much in the job log than to be missing the one or two details that might have really helped.
Earlier this year, I was asked to look at an ASP.NET error that was very strange. It was occurring at the point where a database connection was opened to the IBM i, using the IBM .NET Provider. Unfortunately, the error would only occur intermittently, with absolutely no discernible pattern. Those types of errors cause a sinking feeling in your heart. You can never be completely at ease that the code will work.
When I have an unexplained error that I can't figure out how to replicate, I first start looking into the current state of my system. By that I mean, what is state of the fixes? It is especially difficult in a client/server environment, as I need to consider fixes for the database provider, fixes for the .NET Framework itself, fixes for the version of Windows, and fixes for the IBM i. I am usually up-to-date on my development systems, but when I talk with others who are having problems, it seems that most have no idea of what they're actually running at the time. There is tremendous confusion over the version of System i Access for Windows and the Service Level that is installed, which is one reason why, in this month's companion article, I've posted links to documents that show you exactly how to determine what version you are working with. The importance of tracking down this information is that I've found, particularly with the IBM .NET Provider, that there are many fixes that may address the very problem you are having. This is by no means a "knock" at IBM; it is very difficult to develop database provider code and account for all of the variations that occur out in the field. Far less frequently, there are PTFs available for the IBM i that may correct the problems with connections. I've found that most of the required fixes are on the client side.
So what next, if you find that everything on the client and the IBM i is as up-to-date as you can get it? At that point, you'll need to work with exception handling in your programs.
One of the first things you learned when you got started with Visual Basic .NET or C# was the Try/Catch (and Finally) construct. Simply put, you "try" to perform one or more statements, and you "catch" any errors. "Finally", regardless of the occurrence or nonoccurrence of errors, you perform clean-up operations.
Figure 1C (C#) (.pdf) and Figure 1V (VB) (.pdf) show a simple Console application (named NoExceptions) that does not handle exceptions. The output of either program is shown in Figure 1. In this test, the database file name is incorrect; it should be qiws.qcustcdt. As you can see in Figure 1, there is no indication of any error; the program simply runs to completion. The problem with this program is not that the database file name is incorrect, the problem is that the Catch block is empty. This is an example of "swallowing the exception". Because of the presence of the Finally block, it would be possible to simply omit the Catch block, the effect would be the same. I seriously doubt that anybody reading this article would put code like this into production.
But sometimes, code like this happens "by default." We are in a hurry; we are trying to get this code working for a proof-of-concept. We know that we should be handling errors, but I just ran the code and it worked OK without a good Catch block. We'll get back to it when we have time. So the code makes it into production. There is no way to know if it is ever working or not, since it mutely passes right over any errors. If this type of code is used in a lengthy program, or in conjunction with several other programs, it can be very difficult to track down the error, as this program certainly isn't complaining.
So we try to act a little more wisely, and add some minimal error reporting to our code. Figure 2C (.pdf) and Figure 2V (.pdf) ("MinimalExceptions") show a Catch block that actually reports the error, with the result shown in Figure 2. In this example, there is little doubt that an error occurred, and chances are very good that you will be able to track down the error without too much trouble. In this example, the e.Data property doesn't add any useful information, but it might be good for other types of errors.
This exception handler uses the generic System.Exception class, which has several additional properties that you might want to capture. For example, the e.StackTrace property can help you track through your code to the routine where the error occurs. You can look up the properties of System.Exception in the MSDN documentation, or simply use Visual Studio IntelliSense prompting to show the list of properties.
Although this type of exception handling is far better than nothing, it is still not quite as good as it gets. The problem is that System.Exception is essentially the same as the IBM i CPF0000 message; it is simply a generic notification that there has been an error. In most cases, as is shown in Figure 2, there will be enough information to determine what the problem is and how to correct it. However, for production-worthy code, you need to at least consider using the full arsenal of error handling tools that the .NET Provider makes available.
Figure 3C (.pdf) and Figure 3V (.pdf) ("AllExceptions") show the same application, only this time, all of the possible exceptions defined for the IBM .NET Provider are accounted for. The output for this error handling, as shown in Figure 3, is truly majestic. The message details data is the same as you get with second-level logging in an OS/400 job log.
One of the first things you should notice in the program is the extensive list of iDB2 exceptions that are available. All of the exception handlers call the WriteException subroutine to display the complete error information. Note that two of the exceptions are only available with the V6R1 version of the provider.
One of the questions that you may have when looking through the list of exception is, how can you test the exceptions? I briefly considered developing a test case for each exception, then decided I didn't really need to do that, and I don't think you would need to either, except as an exercise. What you're really after is simply the inclusion of the exception and its handler. Rather than try to pick and choose which of the exceptions are "likely" to occur, why not just include the entire list? Consider the case of the intermittent failure: nobody could come up with a good reason for the error, or even start to narrow down what to look for. Rather than study the list of exceptions, just do the five minutes of copy-and-paste coding that it will take to get the entire list into your program. You will still want to include the generic Exception handler. With the program outfitted like this, I will be much happier the next time the intermittent error occurs. If the error is for one of the specific iDB2 exceptions, I will learn what the exact exception is and capture as much data as possible. On the other hand, if the error is not an iDB2 exception, I'll find that out also.
The WriteException routine is a simple logging routine that displays most of the properties that are available with the iDB2 exceptions. I omitted the StackTrace property just so that the figure would be usable for this article. In actual practice, I would write the StackTrace as the last of the properties.
The Errors collection deserves special mention. It is a property of an iDB2 exception, and it may contain zero to many additional errors. Conceptually, it is somewhat the same as the IBM i technique of cascading messages, where a lower-level message triggers the next higher level, and up to where you finally get an error. As you can see in Figure 3, in this case there is only one error, and it reports the same information as the main exception properties. Nevertheless, you never know when there might be additional nuggets of useful information, so it is worth including the simple iteration pattern to go through all of the additional errors that are available.
So how do you think the programmer and the team that developed the rocket code felt? The triumphant feeling of finding the error was probably greatly diminished by the precipitating circumstance. Would better error handling have helped? Do you think the people who wrote that code now think differently about error handling? For myself, I have the frightening recognition that I could have written code like that; I would hope to learn from their mistake, rather than repeat it.