Don't Commit Errorcide, Part I
Errorcide occurs when an application kills itself due to the misapplication of error handling code. No error handling code and your application is doomed to the scrap heap. Too much, misapplied, or a poor application of error handling code and your error handling code causes the bugs. Perhaps the physicians' adage at least do no harm is a good way to think about error handling code.
Error handling code should make your application better but never introduce problems. In Part 1 of this two-part article, I will demonstrate the syntactical concepts of structured exception handling and how a "less is more" approach will help you make your applications more robust without introducing new errors.
Error handling is still a pretty subjective art form. It is a challenging prospect to prove that an application has too little or too much structured exception handling code. The art works well when exception handling code is applied in conjunction with problem-solving code and then is fine tuned to remediate specific problems during testing and maintenance. After reading this article, you will have a better understanding of the technical aspects of using structured exception handling, and the art will take practice. In the interim, your applications should not commit errorcide.
Basic Exception Handling Syntax
Recently, I received an e-mail from a VB6 programmer who felt betrayed by Microsoft. This reader was one of those who believed Visual Basic .NET should be called Visual Fred and assured me that he wouldn't be switching to VB.NET. I empathize. It is hard to be a master and then have to return to novitiate, especially if the calendar is long. But, Visual Basic .NET is worth switching to, and one of the reasons is structured exception handling.
VB.NET uses a class to convey information about the type of the error. Instead of a single, one-size fits all Err object, each class of error can be captured by a specific class. This permits developers to extend the error classification system to suit it for each problem domain. The number of types is unlimited. For example, if your application must know the identity of the operator or the thread the exception occurred on, the developer can implement such a class of error by sub-classing an existing exception base class. For a complete examination of the differences between VB6 structured errors and VB.NET structured exceptions, we would need another whole article. We'll leave further examination of the comparisons perhaps for another day or venue.
To begin using structured exceptions, it will be helpful to review the basic grammar of the fundamental, recommended exception handling code in VB.NET. (It is worth noting that VB6-style error handling is supported in .NET, but I suspect this will ultimately go the way of the dinosaur.) Listing 1 demonstrates the basic structure of an exception handling block.
Listing 1: A basic example of an exception handling block.
Try File.Delete("B:\FILE.NOT.EXISTS") Catch Console.WriteLine("Error") End Try
In the example, the code tries to delete a file on the B: drive. (Actually, deleting a non-existent file on a valid drive does not raise an exception. It is an acceptable operation.) The basic behavior here is that the code between Try and Catch is tried. If something goes wrong, the code between Catch and End Try runs and the error is considered handled (in the scenario shown). Think of the Try block as the man on the high-wire at the circus and the Catch block as the net below. If something goes wrong, it is nice to have that net.
Other forms of exception handlers include Try and Finally, Try, Catch, and Finally, and Try, more than one Catch, and optionally a Finally. We'll cover these variations in this article. It is important to understand at this juncture that every subroutine or function does not need, nor should have, all or even any of these elements. Some subroutines and functions will benefit from structured exception handling, and in some cases it is added noise. When to use these features is a subjective measure of good taste and is the subject of the rest of this article.
Implementing the Try Block
The Try Finally block is referred to (by some) as a resource protection block. Try Finally without a Catch block takes the following form:
Try // do something that needs cleanup Finally // cleanup here, always! End Try
The Finally block is called a resource protection block because that is its purpose. Code in the Finally blocks is always run. Hence, if you need to do something such as ensure a file, socket, or database connection is closed, you want to use the Try Finally construct. The basic rhythm of the Try Finally construct is demonstrated with code and comments next:
// create a resource that you want to protect Try // try to use the protected resource Finally // always clean up the protected resource End Try
The example in Listing 2 demonstrates the rhythm of the resource protection block. In the listing, a text file is created. In the Try block, we attempt to write something to the text file. Whether the write succeeds or fails, the Finally block is executed and the file is closed.
Listing 2: A Try Finally block protecting the finite number of file handles.
Public Sub FinallyHandler() Dim MyTextFile As TextWriter = File.CreateText("some.txt") Try MyTextFile.WriteLine("Welcome to Valhalla Tower Material Defender!") Finally MyTextFile.Close() End Try End Sub
The reason the resource is created outside of the Try block is two-fold. First, if we can't even create the file, an error occurs and we never enter the Try block. In the first scenario, the resource isn't created so there is no need to protect, and, trying to protect an uncreated resource can itself cause errorcide. In the example, if we declared the TextWriter outside of the Try block but created the instance inside of the Try block and the file could not be created, we get an error indicating the file couldn't be created and an error when we try to close the un-initialized TextWriter, resulting in death by error handler, or errorcide. The second reason the resource is declared outside of the try block is that the Try and Finally blocks have individuated scope. Variables declared in Try cannot be seen in Finally. Yet the subroutine in the example has an outer, containing scope and the Try and Finally blocks have inner scope. Something in the subroutine's outer scope—our resource to be protected—can be seen in the inner scope of both Try and Finally.
What happens in our example if we don't use the Finally block? The answer is that there is no guarantee that the TextWriter.Close method is called. Consequently, you may find that the text file appears to be empty. This is because closing the file flushes—programming vernacular is so colorful—execute, flush—it, ensuring that all of the text is actually written, and that the file is now available to some other process.
Page 1 of 2