Yair Alan Griver
Flash Creative Management Inc.
Writing reusable code is one of the biggest opportunities for developers to increase productivity and quality for new and existing development efforts. This article will focus on how to write reusable objects in Microsoft® FoxPro® version 2.0, paying particular attention to the use of the power tools in the development process. We will cover the maintenance issues raised by FoxPro's environment handling commands, and discuss how the management and use of reusable code affects the development life cycle.
One of the keys to creating reusable objects is that they must not affect the procedure that is calling them. This leads to a few design decisions that must be made.
Any variables from a higher-level procedure must be passed. Do not assume that what you need will be there. Remember, we are writing black-box objects. You pass information to them, and they return information without affecting the calling procedure.
For instance, if you need a routine that squares a number for an application, you can code it specifically for the current application:
lnSquareMe = 9
lnSquared = sqr()
FUNCTION sqr
RETURN lnSquareMe * lnSquareMe
The above routine will work for the current application, but if you have to use it again in a future application, you'd better be sure to have a variable called lnSquareMe. It's much better to write the application this way:
lnSquareMe = 9
lnSquared = sqr2(lnSquareMe)
FUNCTION sqr2
PARAMETER lnValue
RETURN lnValue * lnValue
You now have a routine that will work in any application, for any value that is passed.
When writing reusable code, you must be sure that you don't affect the higher procedure. This means that you must be sure that any variables you use in the current procedure aren't used in a higher procedure. This can seem very difficult—you never know what may call this piece of code!
The solution is to be very careful about naming every variable PRIVATE. This will hide any instance of the variable that may have been created in a higher procedure.
Note I can't emphasize this one enough. Debugging an application that fails because a variable in a black box is changing a higher variable is one of the toughest things to do. Take the time to go back into the code for the object and make sure that every variable is declared PRIVATE.
Microsoft® FoxPro®'s environment handling commands are very powerful. However, with power comes responsibility. For instance, you can't assume that an application will always have EXACT set ON. If your reusable object requires that one of the SET or ON commands be set a certain way, do it yourself. When you do so, save the previous setting, and restore it on exit. For instance, if your routine requires that EXACT be ON for an ASCAN() on an array, do this:
PROCEDURE LookArry
*-- This procedure takes 3 parameters:
*-- laPassed = An array
*-- lcFind = An expression to search for
*-- lnCol = The column to return
*--
*-- Note: After this proc, you must test for EMPTY(value)
*-- to see if it was found.
PROCEDURE laPassed, lcFind, lnCol
PRIVATE lcOldExact, lnFound, lnRow, lcRetVal
lcOldExact = SET("EXACT")
lcRetVal = ""
lnFound = ASCAN(laPassed, lcFind)
IF lnFound # 0
lnRow = ASUBSCRIPT(laPassed,lnFound,1)
lcRetVal = laPassed[lnRow,lnCol]
ENDIF
SET EXACT &lcOldExact
RETURN lcRetVal
The screen builder makes it much easier to develop more complex reusable objects than ever. You can focus on what the object should do, rather than how it looks.
Here are a few rules to follow when writing a reusable screen:
We've found that screen design goes much more quickly if you design in the following manner:
One of the key issues to remember when using reusable objects is that they may call other reusable objects. The project builder will automatically search for any referenced screens, procedures, and so on, and will include the items in the project for you. This makes the use of reusable objects much easier—you don't have to worry about forgetting a necessary object. Just call it—if you're using the project builder (and you should be), it will be there when you need it.
Parameters in FoxPro provide a hidden boost in modifying and maintaining reusable code. FoxPro allows you to pass fewer parameters than are called for in the routine. The PARAMETER() function lets you test for the number of parameters actually passed. Using this ability, you can add new functions to existing objects without changing their original purpose.
In other words, if you have been using the LOOKARRY() routine that is listed above, and suddenly need to be able to (occasionally) force it to not change the EXACT setting, you can simply add a fourth parameter, llNoExact. If it's .F., don't change a thing. If it's .T., ignore the SET EXACT line. The beauty of this is that by adding the fourth parameter, and making its default (.F.) the same as things were, you don't have to change any of the calling lines in all of the applications that used LOOKARRY().
Note This is another maxim of writing reusable code. Don't break anything with your "enhancements."
Data-driven reusable objects are more complex and more useful forms of reusable code. These typically perform a function that is used in multiple applications, but vary the data that they act upon based on information in .DBF files.
For instance, you can store report information in a DBF, allowing you to create a generic reporting function that works in every application. Reporting is a common task in applications—only the reports themselves differ. Therefore, create a reusable reporting module, and have the information that changes (for example, the report names, filters, and so on) change by storing that separately.
Let's look a little more closely at another example of a data-driven portion of code. One typical case would be a routine that creates any DBFs that don't exist. In this manner, if the program is just being installed, or you forget to give your user a DBF, your routine can create it for you. It would be nice if we could write the code once, and simply fill a DBF for each application. This way, we don't have to worry about bugs creeping into our code.
To create this routine, let's look at the data file that is needed:
Structure for database: C:\BOOK\CD\DBFS\DATADICT.DBF
Number of data records: 17
Date of last update : 07/11/91
Memo file block size : 64
Field Field Name Type Width Dec Index
1 FIELD_NAME Character 10 Asc
2 FIELD_TYPE Character 1 Asc
3 FIELD_LEN Numeric 3
4 FIELD_DEC Numeric 3
5 DBF_NAME Character 8 Asc
** Total ** 26
The first four fields can be quickly created through the COPY STRUCTURE EXTENDED or AFIELDS commands. The fifth field contains the name of the DBF for the current FIELD_NAME. Note that with only these five fields, we can write a program that creates all of our DBFs on the fly if they don't exist:
PROCEDURE CreaDBF
*--
*-- Routine to create any missing dbfs
PRIVATE lnArryCnt, lcDBF
SELECT DISTINCT datadict.dbf_name ;
FROM datadict ;
INTO ARRAY laDbfList
FOR lnArryCnt = 1 TO ALEN(laDbfList)
lcDBF = laDbfList[lnArryCnt] - '.DBF'
IF NOT FILE(lcDBF) && DBF doesn't exist
SELECT * ;
FROM Datadict ;
WHERE Datadict.dbf_name = laDBFList[lnArryCnt] ;
INTO ARRAY laFields
CREATE TABLE laDBFList[lnArryCnt] ;
FROM ARRAY laFields
RELEASE laFields
ENDIF
ENDFOR
We can, of course, expand on this model, adding fields that store indexing information, and full field names. Using this additional information, we can create automatic re-indexing routines, a window that allows the user to change the active index on the fly, and a simple query builder that allows the user to select fields for a query. This shows the power of data-driven design and reusable objects: one DATADICT.DBF could be created, automatically enabling all four of the above functions.
Hint When creating reusable objects that use data-driven design techniques, assign a default action to the object. In other words, write it so that it will work even without the data file. Even if it simply exits with a WAIT WINDOW "No data file found", at least it will not crash if called when you are originally prototyping an application.
Committing yourself or your company to utilizing reusable code requires a commitment to "managing that code." Having a reusable reporting engine is not very useful if only you know about it. You need a way to disseminate information about these procedures to others in your group or company. It is also useful to have a set location for this reusable code.
We set up a COMMON directory on our network. Underneath that directory are a number of other directories: SCREENS, MENUS, REPORTS, and so on. This lets us compartmentalize the location of our reusable code. We can back it up quickly and easily. We have also mapped one of our network drives to this COMMON directory, allowing us to access it quickly when necessary.
An object manager is a focal point for your reusable code tracking. It can be as simple as a DBF that contains the name of the object, what it does, some key words for searches, calling conventions, and return value. It should also contain a field that tracks the changes to the object over time.
Here again, putting some structures in place can ease development. Using the COMMON directory structure described above, you can easily write a routine that automatically moves information into the object manager. If you standardize a commenting convention for the header, your routine can even fill in most of the information for you.
Writing reusable code affects the entire development organization. It increases productivity and decreases maintenance efforts. Using the techniques outlined above will help you show a marked gain in the quality and quantity of applications your organization can develop.