Developing Reusable Objects

Yair Alan Griver
Flash Creative Management Inc.

Abstract

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.

Coding Issues

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.

Calling Your Code

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.

The PRIVATE Trap

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.

SETting Your Environment

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 and Reusable Code

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:

  1. Draw your screen. Decide what it will look like before adding any code.

  2. Ask yourself what your user should see when the screen first appears. Whatever code has to run before the screen appears goes in your SETUP.

  3. Click on each screen object and decide what should happen when the user does so. Put that code in the VALID/WHEN snippets for the object.

  4. Decide what has to happen when the screen goes away. Put that in the CLEANUP snippet.

  5. Congratulate yourself on how easy it was to create the screen.

The Project Builder and Reusable Code

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.

Parameter Usage

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

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.

A Practical Example

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.

Managing Reusable Code

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.

A Directory Structure

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

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.

Summary

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.