Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Structured Error Handling in VFP 8

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, seven years later, more sophisticated error handling mechanisms take center stage as Visual FoxPro 8.0 introduces structured error handling.


advertisement

isual FoxPro 8.0 introduces a new error handling mechanism known as "Structured Error Handling." This mechanism uses Try/Catch blocks to wrap sections of source code and attach the appropriate error handler. 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

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()

Each Try block needs to have at least a CATCH or a FINALLY block.
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.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date