With the introduction of Visual FoxPro 3.0, error handling in VFP changed substantially.
Rather than using "on error" statements, "state of the art" error events became available. Now, 7 years later, more sophisticated error handling mechanisms take center stage as Visual FoxPro 8.0 introduces structured error handling.
Handling potential errors in the most graceful way has been a goal of software developers since the very early days of programming, and the quest for the perfect methodology is still ongoing. FoxPro and Visual FoxPro have gone through a number of different ways to handle errors (all of which are still available today and are useful for different scenarios).
The "most traditional" way to handle errors in FoxPro (and even in FoxBase, before) was the ON ERROR statement. This command tells FoxPro what to do in case of an error. Arguably one of the most common scenarios would be to call a procedure that handles an error in the following fashion:
ON ERROR DO ErrorHandler
Each Try-block needs to have at least a CATCH or a FINALLY block.
Or, you might use a slightly more sophisticated version, as suggested by the Visual FoxPro documentation:
ON ERROR DO errhand WITH ; ERROR( ), MESSAGE( ), MESSAGE(1),; PROGRAM( ), LINENO( )
Of course, in the object-oriented world that Visual FoxPro lives in, this is a very procedural way to handle things. Luckily, the ON ERROR command can evaluate any Visual FoxPro expression, including calling methods on an object:
ON ERROR oErrorHandler.Handle()
This approach works rather well in scenarios where a global error handler is used. However, this type of error handler is generally not used in an object-oriented environment. There are a number of reasons for this. First of all, in order to create black-box objects, those objects have to handle their own errors to conform to the rules of object-oriented development. However, to make those objects handle their own errors, we would have to set up an error handler like so:
ON ERROR THIS.HandleErrors()
Unfortunately, this doesn't work, because whatever is specified as the ON ERROR statement will run as if it were a separate procedure. In other words, this line of code will run outside the object. Therefore, the THIS pointer is not valid.
Another issue is that the ON ERROR statement would not be scoped to the object. Consider the following example:
ON ERROR * && Ignore errors LOCAL loExample loExample = CREATEOBJECT("Example") xxxxxxx && Syntax error RETURN DEFINE CLASS Example AS Custom FUNCTION Init ON ERROR THIS.HandleErrors RETURN .T. ENDFUNC FUNCTION HandleErrors MESSAGEBOX("Handling Errors...") ENDFUNC ENDDEFINE
In this particular example, the first line instructs VFP to ignore all errors (the error statement is an asterisk, which is a comment line). Then, the Example object gets instantiated, and its constructor (Init()), sets the error statement to "THIS.HandleErrors" (for now, let's just assume that would be a valid line of code). After the object is instantiated, a line of code with a syntax error ("xxxxxxx") executes, raising an error.
The question is: What error handler will handle that error? Since we now know that ON ERROR is not scoped to objects, we also know that the error is handled by "THIS.HandleErrors". Clearly, even if that would call a method on the right object, this wouldn't be desired, since the object has no knowledge about how to handle errors that may occur outside the object. Similarly, error handlers defined after the object is instantiated would throw off error handling within the object. Neither scenario will allow us to create black box objects.
One possible solution would be to create an object devoted to error handling. This object could be created when the main object gets instantiated. However, to make this work, the new object would have to be referenced through a public variable so it could be referenced (again, THIS.oError.HandleErrors() would not work). This could lead to collisions with other objects that employ the same approach. Also, each individual method would have to set the error handler to that handler object, and reset it back not only when the method completed, but also every time the object called out to other code (which may or may not use a similar approach). This certainly would be an error-prone solution. Let's not even investigate it any more, although I could point out a long list of other problems.
Clearly, a better way to handle errors was required. For this reason, Visual FoxPro 3.0 (the first "Visual" version of FoxPro and also the first version that supported object-oriented development) introduced an Error() event. Using that mechanism, errors could be handled in the following fashion:
ON ERROR * && Ignore errors LOCAL loExample loExample = CREATEOBJECT("Example2") xxxxxxx && Syntax error RETURN DEFINE CLASS Example2 AS CUSTOM PROCEDURE INIT xxxxx && Syntax error ENDPROC PROCEDURE ERROR(nError, cMethod, nLine) MESSAGEBOX("Error inside the object") ENDPROC ENDDEFINE
The idea here is simple: Whenever an error occurs anywhere within the object, the Error() event will fire. If the error occurred anywhere outside the object, it will be handled by whatever error handler is defined there. In our example, the syntax error in the Init() method will be handled by the Error() method, and the syntax error in the line of code after the CreateObject() will be handled by the ON ERROR error handler (which will actually hide the error).
This mechanism has a number of advantages. First of all, it allows building self-contained objects. Secondly, it splits the gigantic task of handling errors globally, into smaller, more digestible pieces. No longer are we dealing with handling a very large number of errors. For instance, if the object at hand doesn't deal with database tables, we probably don't have to worry about handling any database errors.
However, this approach also has some problems. For example, it still may be handling errors on a scale much larger than we want. Objects can be large and do a large number of different things, each of which may have only a very limited number of scenarios that may go wrong. In total, however, the object might require a very complex error handler.
Another problem is that this type of error handler makes it very difficult to "exit gracefully" whenever an error has occurred. Consider the following example:
DEFINE CLASS WordExport AS Custom FUNCTION Export(lcText1,lcText2) LOCAL oWord as Word.Application oWord = CREATEOBJECT("Word.Application") oWord.Application.Visible = .T. oWord.Documents.Add() oWord.Selection.InsertAfter(lcText1) oWord.Selection.InsertAfter(lcText2) RETURN .T. ENDFUNC PROCEDURE ERROR(nError, cMethod, nLine) MESSAGEBOX("Error exporting to Word!") ENDPROC ENDDEFINE
The idea behind this simplified example is that the WordExport object can be used to create a Word document on the fly. To do so, the developer simply instantiates this object and passes some text to the Export() method. The method then opens an instance of Word, makes it visible, creates a new document and exports the text.
What would happen if the user actually closed Word right after a new document has been created (right after the Documents.Add() line)? Well, the next two lines of code would both cause an error (and so would hundreds of other lines if this was a life-size example).
But what could our error handler do to solve the problem? Well, beyond displaying the error in a message box, the error handler could try to fix the problem. However, this is unlikely in this case, because in order to do that, the method would have to start over from scratch. Since that isn't something the error handler could do easily, it can choose to ignore the error and proceed with the next line of code, which would then cause another error that could also be ignored, and so forth.
Another option would be to issue a RETRY, which would run the line that failed again, causing another error, which would result in an endless loop if the handler just tried to RETRY again. The only other option we have would be to CANCEL, which would shut down the whole process and not just the current method.
Note also, that the method returns .T., which is the way I would like things to be if the document got created successfully. However, I would like the method to return .F. if there was a problem. This isn't so easy, since the Error() event doesn't have any access to the return value of this method.
One possible solution would be a local ON ERROR statement instead of the error method:
DEFINE CLASS WordExport AS Custom FUNCTION Export(lcText1,lcText2) * We create a new error handler LOCAL lError lError = .F. ON ERROR lError = .T. * We run the regular code LOCAL oWord as Word.Application oWord = CREATEOBJECT("Word.Application") oWord.Application.Visible = .T. oWord.Documents.Add() oWord.Selection.InsertAfter(lcText1) oWord.Selection.InsertAfter(lcText2) * We reset the error handler, * and check if everything went fine ON ERROR IF lError RETURN .F. ELSE RETURN .T. ENDIF ENDFUNC ENDDEFINE
This is an acceptable solution, but there are difficulties with this approach. First of all, the method might call out to other methods that may reset the error handler or point to a different handler. This is a problem that is hard to avoid, since you may not have control over other code that is running.
Also, at a later point in time, someone may want to add an Error() method to this object (perhaps to handle errors that may occur in other methods). The problem with that is that the error method takes precedence over the ON ERROR handler, hence rendering the ON ERROR useless.
To solve these issues, Visual FoxPro 8.0 introduces "Structured Error Handling." This approach allows the developer to wrap a series of commands into a block that is handled by a local error handler. The advantage of this error handler is that it usually handles a very limited set of potential problems, making it simple and straightforward.
This is the basic syntax for structured error handling in Visual FoxPro:
TRY * Do something CATCH * Handle a potential problem ENDTRY
Let's see how we could re-work the above example into a scenario handled by a Try/Catch block:
DEFINE CLASS WordExport AS Custom FUNCTION Export(lcText1,lcText2) LOCAL lReturnValue lReturnValue = .T. TRY * We run the regular code LOCAL oWord as Word.Application oWord = CREATEOBJECT("Word.Application") oWord.Application.Visible = .T. oWord.Documents.Add() oWord.Selection.InsertAfter(lcText1) oWord.Selection.InsertAfter(lcText2) CATCH lReturnValue = .F. ENDTRY RETURN lReturnValue ENDFUNC ENDDEFINE
As we can see, this is a much simpler way to implement the solution. First of all, it is simply much less "kludgy" and is a very clean implementation. But more importantly, it is a much superior implementation from a technical point of view. The solution is not influenced by outside error handling.
Also, we have full control over what is to happen if an error does occur. Unlike in the example with the error event, we can write code within our method that executes no matter whether an error occurred or not, making it easy to set the return value to our liking. (We were able to do this in the previous example, but the solution was error prone and easy to break by running it in different environments).
Try/Catch blocks can be nested to achieve more granular error handling.
I'm sure that by now you already have a good idea about what Try/Catch does: Whatever code we run inside a Try-block will execute until an error occurs. If an error does in fact occur, the Catch-block is executed. Note that the try block stops executing as soon as an error occurs. There is no way to retry or ignore the error. If that's what you would like to do, Try/Catch error handling is not the right solution.
Note that the Catch-block is never executed if no error occurs. Sometimes you might want to define code that runs as cleanup code, whether an error occurred or not. Here is an example:
DEFINE CLASS WordExport AS Custom FUNCTION Export(lcText1,lcText2) LOCAL lReturnValue lReturnValue = .T. TRY * We run the regular code LOCAL oWord as Word.Application oWord = CREATEOBJECT("Word.Application") oWord.Application.Visible = .T. oWord.Documents.Add() oWord.Selection.InsertAfter(lcText1) oWord.Selection.InsertAfter(lcText2) CATCH lReturnValue = .F. FINALLY IF VarType(oWord) = "O" oWord.Application.Quit() ENDIF ENDTRY RETURN lReturnValue ENDFUNC ENDDEFINE
In this example, we shut down Word, even if something went wrong. Note however, that the error may have occurred before Word ever got instantiated. Therefore we need to first check whether Word is an object. (Actually, things may be a little trickier with automation objects, especially Word, but for simplicity we'll leave it at that.)
At this point you may wonder why we need a Finally-block. After all, we could have put that code after the ENDTRY and would have achieved an identical result. However, there are scenarios that can greatly benefit from using the finally-block (which we will examine further down), making the use of FINALLY a good idea in general.
One last remark about the basic Try/Catch structure: Each Try-block needs to have at least a CATCH or a FINALLY block. Therefore, you can not just say "try this, and I don't care of it works or not since I can't do anything about a potential problem anyway." If you would like to do that, you can create a Catch-block that has nothing but a comment. A scenario like this may be desired within an error handler:
TRY USE Customer LOCATE FOR LastName = "Gates" IF FOUND() StrToFile("Gates found!","customer.log") ENDIF CATCH TRY StrToFile("Error: "+Message(),"Error.log") CATCH * Nothing we can do now ENDTRY FINALLY IF Used("Customer") USE IN Customer ENDIF ENDTRY
This example also demonstrates one of the key features of structured error handling: Nested Try/Catch blocks.
Nested Try/Catch Blocks
Try/Catch blocks can be nested to achieve more granular error handling. There may be a Try/Catch block around the entire application, there may be Try/Catch blocks wrapping entire methods, then there may be individual blocks, and so forth.
Let's enhance our Word example a little more and instead of creating a blank document, we will create a new one based on a certain template:
FUNCTION Export(lcText1,lcText2) LOCAL lReturnValue lReturnValue = .T. TRY * We run the regular code LOCAL oWord as Word.Application oWord = CREATEOBJECT("Word.Application") oWord.Application.Visible = .T. TRY oWord.Documents.Add("MyTemplate.dot") CATCH oWord.Documents.Add() ENDTRY oWord.Selection.InsertAfter(lcText1) oWord.Selection.InsertAfter(lcText2) CATCH lReturnValue = .F. ENDTRY RETURN lReturnValue ENDFUNC
In this example, the inner Try/Catch block traps only errors that may occur while a new document is created based on the specified template. Presumably, if that template doesn't exist, an error will be raised and caught by the Catch-block, which will create a blank document. The code then proceeds as planned.
Note that the Catch-block may raise another error that will then be handled by the "outer" Catch-block (which simply sets the return value and gives up).
There is one potential problem here. We are assuming that the error has been caused by the fact that the template doesn't exist. But of course, there could be a number of other scenarios causing other problems. For instance, the problem could be caused by the user closing Word right after it became visible (yes, they'd have to be very quick, but hey, this is only an example!). In our little example, this wouldn't be a problem. Worst case, the Catch-block fails again and defaults to the outer handler, which will handle the situation appropriately. However, in many complex scenarios, we would have to look at additional error information and handle the situation appropriately.
Conditional Error Handling
Visual FoxPro has a number of functions to retrieve error information, such as Message(). However, those functions are not really adequate to make this bullet-proof, since nested errors make things a bit complicated. For this reason, Microsoft introduced an Exception object. The exception object can be invoked simply by using it on the CATCH statement:
CATCH TO oException
This will make an object named "oException" available within the Catch-block. This object has a number of properties, such as ErrorNo, Message, LineNo, Details, LineContents, and more. Using this construct, we can use the following syntax to check for errors caused by the template only:
FUNCTION Export(lcText1,lcText2) LOCAL lReturnValue lReturnValue = .T. TRY * We run the regular code LOCAL oWord as Word.Application oWord = CREATEOBJECT("Word.Application") oWord.Application.Visible = .T. TRY oWord.Documents.Add("MyTemplate.dot") CATCH TO oException IF oException.ErrorNo = 1429 oWord.Documents.Add() ELSE * We have a different problem THROW oException ENDIF ENDTRY oWord.Selection.InsertAfter(lcText1) oWord.Selection.InsertAfter(lcText2) CATCH lReturnValue = .F. ENDTRY RETURN lReturnValue ENDFUNC
In this example, we handle only error 1429, which is the one that is raised if the template wasn't there. The question is: What do we do with all other errors? Well, basically, we want it to be handled the same way all other errors are handled within the outer Try-block. Therefore, we need to elevate the error to that level. We can do so using the THROW statement. This will "re-throw" the error, causing it to be handled by the outer Catch-block. (Exceptions elevated using a THROW statement will end up as user exceptions in the outer error handler. See below for more information.)
The WHEN clause of the CATCH statement can utilize any valid Visual FoxPro expression.
This is a pretty simple example. All we really check for is the error number. But, imagine we check for other conditions. For instance, we could try to find another template, or download it from somewhere, and so forth. If all of those attempts fail, we would re-throw the error. If all we wanted to check was the error number, though, we could do something even simpler:
TRY oWord.Documents.Add("MyTemplate.dot") CATCH TO oEx WHEN oEx.ErrorNo = 1429 oWord.Documents.Add() ENDTRY
This will catch only error 1429. All other errors will be automatically elevated to the outer error handler, if there is one. Otherwise, the default VFP error dialog would be shown. Therefore, this is a shortcut that is functionally identical to the version shown in the previous example (except that the exception elevated to the outer handler will not be a user error).
What makes this feature very powerful is that there can be a number of different catch-blocks:
TRY oWord.Documents.Add("MyTemplate.dot") CATCH TO oEx WHEN oEx.ErrorNo = 1429 oWord.Documents.Add("MyOtherTemplate.doc") CATCH TO oEx WHEN oEx.ErrorNo = 1943 MessageBox("Stop closing Word!!!") CATCH MessageBox("Something else happened!") ENDTRY
Note that catch blocks are evaluated from top to bottom, and only one of them will run. Therefore, the chosen sequence is important. If we change this example to the following, we would see unexpected (or "expected" after you read this article) results:
TRY oWord.Documents.Add("MyTemplate.dot") CATCH MessageBox("Something else happened!") CATCH TO oEx WHEN oEx.ErrorNo = 1429 oWord.Documents.Add("MyOtherTemplate.doc") CATCH TO oEx WHEN oEx.ErrorNo = 1943 MessageBox("Stop closing Word!!!") ENDTRY
In this scenario, only the first catch-block will ever be executed, because it is so generic, it will catch all the errors and the subsequent catch statements will never be evaluated.
The WHEN clause of the CATCH statement can utilize any valid Visual FoxPro expression. Note, however, that to avoid having an erroneous catch statement you shouldn't make these statements too complex.
Throwing Custom Errors
As we have seen in previous examples, the new THROW command can be used to elevate errors the error handler chooses not to handle, so an outer error handler (perhaps another Catch-block, or some other type of error handler) can attempt to handle the error. What's not as obvious is that THROW can be used to raise custom errors, allowing us to architect our applications in an entirely different fashion.
Listing 1 shows an example for this technique. In this example, we have a class called CreditCard that simulates a credit card charging object. This object is rather simple. All it has is one method called ChargeCard(), and all that method does is check if the passed credit card number is "12345678". If so, the card is considered valid. This is a simplistic example, but all we are really interested in is the error handling. So let's see what happens when the card number is invalid.
First of all, the ChargeCard() method instantiates a class called CreditCardException and passes some detailed error information to its constructor. This class is defined a little further down and is a simple subclass of the new Visual FoxPro Exception base class. It has a few overridden properties, and one additional one that gets set based on the value passed to the constructor. Once that object is instantiated, the CreditCard class raises an error (exception) using the THROW command and the new exception object as the expression. This will immediately halt the execution of the ChargeCard() method, and invoke whatever error handler is currently in use.
What's not as obvious is that THROW can be used to raise custom errors, allowing us to architect our applications in an entirely different fashion.
So now let's work our way back up towards the beginning of this listing to see how this code is invoked. The listing starts out with the instantiation of the credit card object and a call to the ChargeCard() method. The parameter passed to this method represents an invalid credit card (error handling is easier to demonstrate if things fail). All of this is wrapped into a Try/Catch block.
Note that the Catch-block traps for error 2071. All user-thrown exceptions end up as error 2071. In this particular example, those are all the errors we are really interested in. Of course, there could be other errors occurring, and those are caught by the second Catch-block. In a larger example, there could also be an outer error handler so we wouldn't have to worry about that possibility. The second Catch-block is not required and I just included it because I'd consider it "good form."
So what exactly happens when a user-thrown error occurs and our Catch-block kicks in? Well, first of all, there could be a number of different user-thrown errors, and we are not interested in any of them other than our custom exception. The user defined information is stored in a property called UserValue, which is a variant and could be anything. In our case, it is another exception object, since that's what we threw, but it could be a string or any other value if the exception was thrown in the following manner:
THROW "Something is wrong!"
Since we threw an object, we can now check for detailed information on that object, such as the error number or perhaps even the class. If we discover error number 10001 (which is our custom error number), we can handle it. Otherwise, it is a different user-thrown error, and we really do not know what to do at all, so we simply elevate the error to the next level by re-throwing it.
Note that this example is not bullet-proof. The following line of code may, in fact, cause other errors:
IF oEx.UserValue.ErrorNo = 10001
If UserValue is not an object, or if it is an object but doesn't have a property called ErrorNo, this would result in yet another exception, which would be thrown to an outer exception handler. Note that the outer exception handler would receive a FoxPro error, and not the user thrown error, which would not be a good thing at all.
At this point, you may wonder how UserValue could be an object but not have that property. The reason is simple: Just like one can throw a string or a number as the user value, one could throw any type of object as the user value. The thrown object doesn't have to be subclassed from Exception.
One of the "gotchas" with this type of architecture is that youi should really use Try/Catch blocks to catch these user thrown errors. Technically, you can use ON ERROR to catch our CreditCardException, but it is a bit trickier to do so since no error object is available.
One last word of caution: The use of a THROW statement will always end up as a user thrown error. This means that if you intend to elevate an error from within a catch block to an outer error handler, you may be re-throwing a system error, but it will end up as a user error in the next-level error handler. The original (system) exception object will end up as the UserValue. Of course, to handle these situations correctly, the outer exception handler needs to be aware of this.
Mixing Error Handling Methodologies
Structured error handling is great and will replace traditional error handling in most scenarios. In fact, some modern languages like C# have only structured error handling. However, there are some downsides to structured error handling, such as no intrinsic retry capability. Also, in many scenarios, pre-existing, non-structured error handling may be in place.
So let's look at a few examples of mixed error handling and the effects it may have on your code. Let's start out with a simple one:
TRY ON ERROR MESSAGEBOX(MESSAGE()) xxxxx ON ERROR xxxxx CATCH MESSAGEBOX("Exception!") ENDTRY
In this example, we define an ON ERROR statement within a Try/Catch block ("xxxxx" always represents some kind of error in these examples). What would we expect to happen here?
Most people I present this to would expect the ON ERROR to handle the first problem, and the Catch-block to handle the second error. This is not the case! The Catch-block takes precedence over the ON ERROR and handles both exceptions.
At this point, you may wonder why one would ever define an ON ERROR inside a Try/Catch. In real-world environments, this is a rather common scenario. Consider this example:
TRY ErrTest() CATCH MESSAGEBOX("Exception!") ENDTRY FUNCTION ErrTest ON ERROR MESSAGEBOX(MESSAGE()) xxxxx ENDFUNC
The Try/Catch wraps a simple call to another function (or method). That function apparently has its own error handling using the old ON ERROR methodology. However, the local error handling mechanism used by that function is now taken hostage by our Catch-block. As you can imagine, this may result in some surprising behavior.
We can produce a similar example using the Error() method:
TRY oTest = CREATEOBJECT("TestClass") oTest.Execute() CATCH MESSAGEBOX("Exception!") ENDTRY DEFINE CLASS TestClass AS Custom FUNCTION Execute xxxxxx ENDFUNC FUNCTION Error(nError, cMethod, nLine) MESSAGEBOX(MESSAGE()) ENDFUNC ENDDEFINE
In this example, we are also calling another method that has a local error handler. However, this time the result is opposite from the previous example. The Error() event takes precedence over the Try/Catch and handles the error inside the called object.
So what would happen if we added some structured error handling to the TestClass object?
DEFINE CLASS TestClass AS Custom FUNCTION Execute TRY xxxxxx CATCH MESSAGEBOX("Exception 2!") ENDTRY ENDFUNC FUNCTION Error(nError, cMethod, nLine) MESSAGEBOX(MESSAGE()) ENDFUNC ENDDEFINE
In this example, the new Try/Catch will handle the error since it has been defined at a higher level of granularity.
An interesting question here is, "What happens if that Catch-block re-throws the error?"
DEFINE CLASS TestClass AS Custom FUNCTION Execute TRY xxxxxx CATCH TO oEx MESSAGEBOX("Error!") THROW oEx ENDTRY ENDFUNC FUNCTION Error(nError, cMethod, nLine) MESSAGEBOX(MESSAGE()) ENDFUNC ENDDEFINE
In this example, the Error() method will get a chance to handle the re-thrown error. The outer error handler will not have the opportunity to handle the exception, because it is not possible to elevate the error from within the Error() method because the exception object is not available there. The only option would be to throw a custom error.
I still owe you an explanation of the FINALLY statement. In many scenarios, it may seem as if FINALLY may not really be required, since the flow of the program is likely to continue after the Try/Catch section. "Likely" is the key term here. If a potential error is not handled in a Catch-block (either because there isn't a matching Catch-block or because another exception is THROWn), code after the Try/Catch statements may not be executed at all. Consider this example:
DEFINE CLASS TestClass AS Custom FUNCTION Execute TRY xxxxxx CATCH TO oEx MESSAGEBOX("Error!") THROW oEx FINALLY MESSAGEBOX("Cleanup Code") ENDTRY MESSAGEBOX("More Code") ENDFUNC ENDDEFINE
In this example, the syntax error in the Try-block is caught by the Catch-block, just to be re-thrown again. This means that the very last MessageBox() will never be executed. However, the MessageBox() in the Finally-block will be executed in every case, even if no exception occurred.
Structured Error Handling is one of the most important language enhancements Visual FoxPro has seen in a while. It is very powerful and helps you tremendously in your attempts to produce bullet-proof code.
If you have any questions about this technology, feel free to email me.