Home Web Design Programming Fairlight CMI Soap Box Downloads Links Biography About... Site Map

The Holmes Page CONVERTING TO CA-CLIPPER 5

CA-Clipper Opportunities Tips and Tricks Networking Internal Errors Source Code CA-VO



BACK TO
CA-CLIPPER
PROGRAMMING

Although it may seem pretty scary, there are still lots of programs out there written in the Summer'87 version of CA-Clipper. 
There is a rule of thumb which says "if it ain't broke, don't fix it". However, there are good reasons to convert to CA-Clipper 5, primarily performance, readability, and maintainability. 
The conversion process should not introduce any new bugs -- it should reduce the possibility of them by using new language features. 
Conversion can be done in several orderly steps:
Eliminate private variables
Add table aliases to fieldnames
Add table aliases to function calls
Use new features


Eliminate private variables


Private variables are the bane of the professional programmer. 
The name "private" is a misnomer since they are actually visible to all functions below the one that declares the variables. To clarify, if function A calls function B, then B is "below" A. Thus B inherits the private variables from A. In addition, a private variable that is declared in the top-most function is effectively public, because it is inherited by all functions in the program. 
Because function B has total access to the private variables of function A, these variables are not safe. They can be changed to illegal values or they can be removed from memory entirely. When control returns to function A, the program may crash (and probably will) if "bad" changes were made to the variables. 
Many older programs relied on this inheritance "feature" to pass values between functions. 
From a maintenance and debugging perspective, this is a nightmare. Variables seem to pop out of nowhere. A single function cannot be understood or altered without knowing the complete hierarchy of function calls that reached this point. The "environment", or collection of visible variables, is not necessarily the same every time this function is called. If a different stack of functions culminated at this function, then there will be a different collection of variables visible. 
This "organic" randomness is unacceptable in a professional (mission-critical) program. 
Private variables must be eliminated. 
Luckily, CA-Clipper 5 provides a solution that other Xbase languages do not. The answer is local variables. 
The most important thing about local variables is that they are not inherited by child functions. In order to use a local variable in a child function, it must be passed as a parameter to the child. This is the way it should be. It improves documentation and therefore maintainability. 
Local variables are also implemented in a more efficient manner in CA-Clipper, so there is typically a speed improvement of up to 10 times in certain areas of code. 
Now that you are convinced of the shortcomings of private variables, how can they be eliminated? 
The most effective way to remove private variables is to start at the "inside" of the program, the lowest level. Think of the program and all its functions as an onion, layer on top of layer. You need to start at the center of the onion and work your way out. 
Identify functions that are called by other functions, but do not themselves call any other functions. These functions are probably in a library or toolbox of popular functions. Since they are called many times, any improvements made on them will have large positive impact on the entire program. 
Make the private variables in the toolbox functions into local variables. Any variables left over must be inherited private variables or public variables. Pass them as parameters to the toolbox functions. This will clearly involve changes to the code that calls the toolbox functions. It may be a lot of code. 
Once the lowest-level functions are done, go up to the next level, and continue the process until the top-level is reached. All private variables should be eliminated by that time. 
The benefit of this method is that you can stop at any time (before reaching the top) and still have some performance and maintenance improvements. Also, this method minimizes the introduction of "new" bugs. 
This step is the single most important improvement you can make to old code. If you do nothing else, do this.


Add table aliases to fieldnames


The ultimate goal of the programmer should be to make every line of code stand on its own. It should need no explanation, no documentation, and no surrounding code to explain it. If each line is clear and obvious enough to achieve these targets, then the code has reached the highest level of perfection. 
Of course the code has to work too... 
Anyway, to work towards this objective, the code should be as unambiguous as possible. 
Adding aliases to fieldnames will help. This code:
    ? CUSTOMER->NAME
is clearer and more concise than
    select CUSTOMER
    ? NAME
The line that prints CUSTOMER->NAME can stand alone. 
Additionally, specifying the table name along with the field name eliminates the source of many compiler warnings. This code:
    ? NAME
causes the compiler to emit this warning:
    TEST.PRG(1)  Warning C1003  Ambiguous reference: 'NAME'
which means that CA-Clipper is not sure if the identifier NAME refers to a (private or public) variable or a field in a table. By default, CA-Clipper assumes that it is a field in a table, but there are circumstances where that assumption may fail, so specify the table name as well.


Add table aliases to function calls


Table aliases can also be added to functions. This ability is incredibly useful. 
The old way:
    // If the Customer record is deleted, then
    // remove all Orders for this Customer.
    private cCustNo
    select CUSTOMER
    if deleted()
       cCustNo = CUST_NO
       select ORDERS
       seek cCustNo
       do while found()
          delete
          seek cCustNo
       enddo
    endif
    select CUSTOMER
The new way:
    // If the Customer record is deleted, then
    // remove all Orders for this Customer.
    if CUSTOMER->(deleted())
       do while ORDERS->(dbseek(CUSTOMER->CUST_NO))
          ORDERS->(dbdelete())
       enddo
    endif
The new way has the following advantages:
• The code is shorter, therefore faster, smaller, easier to maintain, easier to read, less to test, less to fail.
• The private variable cCustNo is no longer needed.
• There is no doubt about which value (CUSTOMER->CUST_NO) is being seeked for in which table (ORDERS).
• There is no doubt that an ORDERS record is being deleted (instead of a CUSTOMER record).
• Each line can stand alone (perfection achieved!)
This code is just sample code -- it is missing "nuances" like network record locking. Still, it illustrates the point, doesn't it?


Use new features


Once the previous steps have been taken care of, new language features can be worked into the program. 
An obvious example is to replace dbedit() code with tbrowse. Sometimes this isn't necessary -- dbedit() is written using tbrowse, anyway. 
Input screens can also be re-written to use more features of the Get system, like mouse support, or inactivity time-outs. 
See Tips and Tricks for some more techniques.


Home Web Design Programming Fairlight CMI Soap Box Downloads Links Biography About... Site Map

Site Map Send comments about this site to Greg at gregh@ghservices.com
All pages copyright © 1996-1999 GH Services™   Created 1996/10/01   Last updated 1999/09/30
All trademarks contained herein are the property of their respective owners