Archive for January, 2007

Handling Errors in Visual FoxPro

There is no question that having a custom error handler working behind the scenes in your Visual FoxPro Application is not only a good thing, but essential for good, stable production systems. Oftentimes, these custom error handlers are nothing more than a generic message alerting the user that a problem has occurred. Good designs will then dump the error details to a file or table for later analysis. While this method is a good first step, so much more can be done. Consider the following:

1. Instead of a generic message, write code to work with certain predictable errors. In my code, I like to trap errors such as “E1, File Does not exist”, “E6, Too many files open”, “E1001, Feature is not available”, “E2012 Cannot call SetFocus error”, and “E2028 API call caused an exception”. In these cases, special messages alert the user to what is going on, while code behind the scenes tries to handle the problem. For some errors, a simple RETRY or RETURN works well (like for error code 1001). For others, more work (error code 1) needs to be done.

2. Dumping the error contents to a file or table is a nice idea, but what if the user never tells you they received an error? Without you knowing, you can’t fix it. In a perfect environment, you could email a copy of the error details to the development staff automatically through FoxPro. Messages can also be posted (via XML and web services) to an Internet support site. Simply including tech support’s phone and fax number on the error message may give the operator a little more incentive as well. In the end, no method is foolproof. The important thing to remember is that no one likes an error, and more importantly, no one likes to get the same error over and over.

3. Intermingle all available error handling techniques in the application. This includes TRY…CATCH…FINALLY blocks where appropriate, using form and class level error methods, and even writing bullet-proof code (checking to make sure a table isn’t already open before you USE it, for example). Spending time on this endeavors will pay dividends in the end. Good error handling doesn’t go away, once it is in place it will continue to do its work throughout the life of the application.

4. Leave ON ERROR alone! There is sometimes a need to handle certain potential error cases differently. To achieve this, developers often swap ON ERROR routines. Take this typical case:
The global ON ERROR points to some code that displays a generic message when an error occurs. In a procedure, the developer needs to try to open a table for EXCLUSIVE use. So, the developer wraps the USE…EXCLUSIVE command around a customized ON ERROR that handles the error case when a file is in use. Next, the developer sets the ON ERROR command back to the global handler.

*-- Set up global error handler
ON ERROR oApp.gobal_err_hand(ERROR()    ,;
                             MESSAGE()  ,;
                             MESSAGE(1) ,;
                             LINENO(1)  ,;
                             SYS(16)    ,;
                             SYS(2018)  ) 
 
*-- Start event processing (run the app!)
READ EVENTS                       
 
*-- function to open a table exclusively
FUNCTION open_data_exclusive()
 
    LOCAL lcOldError
    lcOldError = ON("ERROR")
 
    ON ERROR MESSAGEBOX("Can't open the file!")
    USE sometable IN 0 EXCLUSIVE
    ON ERROR &lcOldError
 
RETURN USED('sometable')

This method has many flaws, but certainly gets the developer where he or she needs to be. But what if the developer forgets to switch the error handler back? What about SET COMPATIBLE? What if a different error occurs?). This method also begs the question: Why not build a single a method with a large CASE structure to handle the specific error conditions? Of course, this is the best approach. Use a single ON ERROR command at the start of the application, and then work with that handler. No switching allowed! Things are easier now in newer versions of FoxPro. TRY…CATCH…FINALLY essentially renders the ON ERROR swap totally and utterly archaic.

Another side-effect of using ON ERROR in this way is that if you’re like me, then you have a debug menu and a debug environment that will treat errors differently. When an error occurs I don’t want to get a generic message. I want the ability to suspend the application and start a debugging session. See my blog entry on the debug menu for more details!

Tags: ,

2 Comments

Interpolation

For those that aren’t familiar with the term, and since I’m not too good at creating my own definitions (go on, ask Andy Kramek!), I thought I’d borrow a definition from wikipedia to get started:

“In the mathematical subfield of numerical analysis, interpolation is a method of constructing new data points from a discrete set of known data points.” (Source)

If you follow that source link to the wikipedia article, you can read lots of nice things about interpolation and how it can be used in the math and science fields. About 8 years ago, I needed such a function: I needed the ability to find a set of values (x) that fell between two known values (f(x)). I wrote the following linear interpolation algorithm in FoxPro to get my answers:

*-- this sets up a test cursor with some data points
CREATE CURSOR crDataSet (fld_x n(4,2), fld_fx n(4,2))
INDEX ON fld_x TAG fld_x
INSERT INTO crDataSet (fld_x , fld_fx) VALUES (1,1.75)
INSERT INTO crDataSet (fld_x , fld_fx) VALUES (3,2.00)
INSERT INTO crDataSet (fld_x , fld_fx) VALUES (4,3.00)
INSERT INTO crDataSet (fld_x , fld_fx) VALUES (5,4.00)
INSERT INTO crDataSet (fld_x , fld_fx) VALUES (7,4.25)
 
? interpolate(2,"crDataSet")   && returns 1.875
? interpolate(3,"crDataSet")     && returns 2.000
? interpolate(3.1,"crDataSet")  && returns 2.100
? interpolate(3.9,"crDataSet")  && returns 2.900
? interpolate(10,"crDataSet")   && returns 4.625
 
FUNCTION interpolate
    LPARAMETERS tnX, tcDataSet
 
    LOCAL lcfld_x , lcfld_fx AS String
    LOCAL lcOldNear , lcOldExac AS String
    LOCAL X1, Y1, X2, Y2 AS Number
    LOCAL lFound AS Logical 
 
    *-- name of fields in tcDataSet cursor
    lcfld_x = FIELD(1)    lcfld_fx = FIELD(2)
 
    *-- important to adjust these settings
    lcOldNear = SET('NEAR')
    lcOldExac = SET('EXACT')
    SET NEAR ON
    SET EXACT OFF 
 
    SELECT (tcDataSet)
 
    lFound = SEEK(tnX,tcDataSet)
    SET NEAR &lcOldNear
    SET EXACT &lcOldExac
 
    IF lFound
        *-- if found, then just return the point
        RETURN EVAL(tcDataSet + '.' + lcfld_fx)
    ELSE
        *- not found, so need to interpolate
        cXStr = tcDataSet + '.' + lcfld_x
        cYStr = tcDataSet + '.' + lcfld_fx
 
        *-- if the seek put us at eof, back up 1
        SELE (tcDataSet)
        IF EOF(tcDataSet)
            SKIP -1
        ENDIF
 
        *-- evaluate the first point
        X2 = EVAL(cXStr)
        Y2 = EVAL(cYStr)
 
        *-- evaluate the second point
        IF RECNO(tcDataSet) = 1
            SKIP +1
        ELSE
            SKIP -1
        ENDIF
        X1 = EVAL(cXStr)
        Y1 = EVAL(cYStr)
 
        *-- draw a line and return the result!
        RETURN ymxb(x1,y1,x2,y2,tnX)
    ENDIF
 
ENDFUNC  
 
FUNCTION ymxb
    LPARAMETERS X1,Y1,X2,Y2,X3
 
    *-- y = mx + b
    LOCAL nSlope , B1
 
    nSlope = (Y1-Y2)/(X1-X2)
    B1 = -((nSlope) * X1 - Y1)
    RETURN (nSlope) * X3 + B1
 
ENDFUNC

There you have it!

A couple of notes: For space reasons, I’ve omitted some key error checking and bullet-proofing (I started doing it, but realized that an extra 20 lines of code for this example was overkill). But it would be a good idea, for example to verify that the columns in the cursor passed were numbers. Come to think of it, you should also very that the correct parameters were passed!

In addition, I remember from math class that such a linear method is not the most accurate method. I am using a straight line between points and not a curve. I suppose a complimentary function could be added along with ymxb to get a curve between the two points, but until I need to do that — I’m not game!

Tags: , , , ,

No Comments