Coding Guidelines

Coding Guidelines

( by Greg Holmes )

Language Syntax 
The general rule of thumb is: built-in features in lowercase, and custom-written functions in mixed case. 
When specifying the complete syntax of a language element in documentation, the input items, parameters, and so on are referred to using the following symbols:

 Symbol  Description
< >  Indicates user input item
( )  Indicates function argument list
[ ]  Indicates optional item or list
{ }  Indicates code block or literal array
| |  Indicates code block argument list
–>  Indicates function return value
 Repeated elements if followed by a symbol
Intervening code if followed by a keyword
,  Item list separator
|  Indicates two or more mutually exclusive options
@  Indicates that an item must be passed by reference
*  Indicates a compatibility command or function

For example:

    len(<cString>|<aArray>) --> nLength

Metasymbols provide a place holder for syntax elements, and they describe the expected data types. A metasymbol consists of one or more lowercase data type designators followed by a mixed case description. This is known as Hungarian Notation.

 Designator  Description
a  Array
b  Code block
c  Character expression
d  Date expression
exp  Expression of any type
id  Literal identifier
l  Logical expression
m  Memo field
n  Numeric expression
o  Object
x  Extended expression

In this example, dnLower and dnUpper can be either date or numeric:

    @...get...range <dnLower>, <dnUpper>
Filenames and Aliases 
All filenames, in any context, are in upper case. Filenames follow DOS naming conventions (preferably limited to letters, numbers, and the underscore).

    use CUSTOMER
    nHandle := fopen('DATAFILE.DAT')

When referring to specific file types in documentation, include the period.
e.g. “A program is stored in a text file with a .PRG extension.” 
Alias names follow the same conventions as filenames, but are limited to A-Z, 0-9, and the underscore. If a filename begins with a number or contains unusual characters, an alias must be specified when the file is opened or an error will result. 
Note that CA-Clipper does not natively support Windows 95 long filenames, although third-party libraries are available to add the capability.

Fieldnames 
Fieldnames are all uppercase, and always include the alias of the table. Fieldnames may contain underscores, but should not begin with one (because the underscore is generally used to indicate an internal symbol).

    @ 10, 10 say BANKS->BRANCH
    nAge := CUSTOMER->CUST_AGE
Memory Variables 
Memory variables consist of a lowercase type designator followed by a mixed case description (see Hungarian Notation). Although CA-Clipper only recognizes the first 10 characters as unique, variable names may be longer.

    cString := "Hello World"
    nYearlyAverage := CalcYearAvg()

If you use Hungarian Notation for your memory variable names and include the table alias with fieldnames, there will be no conflict between the two.

Commands, Functions, and Keywords 
All built-in commands, functions, and keywords are lowercase. In documentation, the font should be Courier or a similar font. If fonts are not available, then bold or CAPITALIZE the word for emphasis. 
Never use abbreviations — this practice is not necessary with a compiler, although it was common in the early days of dBase (which was an interpreter). 
There should never be a space between the function name and the opening parenthesis. Also, note that the iif() function should never be spelled if().

    replace CUSTOMER->CUSTNAME with cCustName
    nKey := inkey(0)

When specifying commands that have clauses in documentation, separate the keywords with an ellipsis (...) and do not include the to clause, unless it is followed by the file,print, or screen keywords.

    copy...sdf
    set message...center
    @...say...get
Programmer-Defined Functions & Procedures 
These begin with an uppercase letter, followed by mixed case letters as appropriate.

    ? StripBlanks("Hello there, this will have no spaces.")

Function and procedure names may contain underscores, but should not begin with one (they may conflict with internal functions which often start with an underscore). There should be only one return statement per function or procedure, and it should not be indented.

    function SomeFunc (...)
      .
      . <statements>
      .
    return cResult

The return value of a function is not enclosed in parentheses, although parentheses may be used to clarify a complex expression.

    return nValue
    return (nCode * 47) + nAnswer
Preprocessor Directives 
Preprocessor directives are lowercase and are preceded by the # sign.

    #include 'INKEY.CH'

Optionally, you may use single quotes around header files that come with CA-Clipper and double quotes around your own. This convention is purely voluntary, but it helps to distinguish between the two. For example:

    #include 'INKEY.CH'
    #include "MY_APP.CH"

Manifest constants are uppercase.

    #define ESCAPE   27
    if lastkey() == ESCAPE

Pseudo-function names should also be uppercase.

    #define AREA(length, width)   ((length)*(width))
Declarations 
Local variables are grouped according to functionality, and may be declared on one or more lines. The declarations appear as the first code at the beginning of a function or procedure.

    procedure Main ( )
    local nTop, nLeft, nBottom, nRight
    local cOldScreen, cOldColor, nOldCursor

Variables may be declared one per line and accompanied by a description.

    local nCount        // Number of records found.
    local nTotal        // Sum of dollars.

The description can be omitted if better variable names are chosen.

    local nRecordCount
    local nDollarTotal

Variables can be initialized when they are declared, although it is often clearer (and safer) to initialize them immediately before they are used.

    local nRecordCount:=0
    local nDollarTotal:=0
Logicals 
The .T. and .F. are typed in uppercase.
Operators 
The in-line assignment operator (:=) is used for all assignments, and the exact comparison operator (==) is used for all comparisons.

    lContinue := .T.
    nOfficeTotal := nRegionTotal := 0
    lDuplicate := (CUSTFILE->CUSTNAME == cCustName)
    if nLineCount == 4  ...
    if left(PRODUCT->CODE, 3) == left(cProdCode, 3)  ...

Although the compound assignment operators (+=-=*=, etc.) are convenient, they should not be used if readability suffers.

    // The traditional way to accumulate:
    nTotal := nTotal + INVDETAIL->PRICE
    // A good use of a compound assignment operator:
    nTotal += INVDETAIL->PRICE
    // But what does this do?
    nVal **= 2

The increment (++) and decrement (--) operators are convenient, but can lead to obscure code because of the difference between prefix and postfix usage.

    nRecCount++
    nY := nX-- - --nX        // Huh?
Spacing 
Whenever a list of two or more items is separated by commas, the commas are followed by a space.

    MyFunc(nChoice, 10, 20, .T.)

Spaces may be used between successive parentheses.

    DoCalc( (nItem > nTotal), .F. )
    cNewStr := iif( empty(cStr), cNewStr, cStr + chr(13) )

Spaces should surround all operators for readability.

    nValue := 14 + 5 - (6 / 4)

In declarations, often spaces are not used around the assignment operator. This tends to make searching for the declaration of a variable easier.

    local lResult:=.F., nX:=0

Thus, searching for “nX :=” would find the lines where an assignment is made, while searching for “nX:=” would find the declaration line (such as the local above).

Indentation 
Indenting control structures is one of the easiest techniques, yet it improves the readability the most. 
Indent control structures and the code within functions and procedures 3 spaces.

    procedure SaySomething
       do while .T.
          if nTotal < 50
             ? "Less than 50."
          elseif nTotal > 50
             ? "Greater than 50."
          else
             ? "Equal to 50."
          endif
          ...
       enddo
    return

Case statements in a do…case structure are also indented 3 spaces.

    do case
       case nChoice == 1
          ? "Choice is 1"
       case ...
          ...
       otherwise
          ...
    endcase
Tabs 
Do not use tabs in source code — insert spaces instead. Tabs cause problems when printing or when moving from one editor to another, because of the lack of a standard tab width between editors and printers. Typically, printers expand tabs to 8 spaces which easily causes nested control structures to fall off the right-hand side of the page. Commonly, a source code editing program will insert the appropriate number of spaces when the <TAB> key is hit.
Line Continuation 
When a line of code approaches the 80th column, interrupt the code at an appropriate spot with a semicolon and continue on the next line. Indent the line so that it lines up in a readable manner.

    set filter to CUSTFILE->NAME  == 'John Smith  ';
            .and. CUSTFILE->STATE == 'OR'

To continue a character string, end the first line with a quote and a plus sign and place the remainder on the next line. Try to choose a logical place in the string to break it, either at a punctuation mark or after a space.

    @ 10, 10 say "The lazy brown fox tripped over " + ;
                 "the broken branch."
Quotes 
Use double quotes for text that needs to be translated (will appear on the screen), and single quotes for other strings.

    ? "Hello World!"
    cColor := 'W+/B'
    SelectArea('PROP')

This is a simple but extremely effective technique because translation departments often want to see the messages in context (in the source code), so the different quote types indicate which messages are to be translated and which should be left alone.

Comments 
Comments are structured just like English sentences, with a capital letter at the beginning and a period at the end.

    // Just like a sentence.
    /* This comment is longer. As you
       can see, it takes up two lines */

You may encounter old-style comment indicators if you maintain older (Summer’87 and earlier) code.

    && This is an older-style of comment indicator.
    *  The asterisk is also old.

For in-line comments, use the double slashes.

    use CUSTOMER            // Open the data file.
    goto bottom             // The last record.

Note that the ‘//‘ of in-line comments begins at column 40, if possible. This leaves enough room for a useful comment.

Source :  http://www.ghservices.com/gregh/clipper/guide.htm

Try these new 5.01 language constructs

Try these new 5.01 language constructs

Array and GET tricks

Array and GET tricks

Simple Clipper Extensions

Simple extensions

Exact comparisons,
Name precedence,
SELECT 0,
Alias functions,
Call by reference and value,
Other Clipper extensions

What a preprocessor is ?

What a preprocessor is, how will operate, and what benefits it will offer ?

Let’s Look at Clipper 5.0’s preprocessor

C5 Preprocessor

What a preprocessor is, how will operate, and what benefits  it will offer ?

Clipper 5.0’s Preprocessor

The Art Of Simplicity

A discussion of how to create objects with Clipper using arrays, and ordinary Clipper syntax. Has several good examples.

An Introduction into Object Oriented Programming.

To me, the challenge of programming is in finding a simple clean way to implement a program. Making sure no matter how complex the specs are, the code itself stays small, strait forward, and easy to maintain.

To illustrate how to reduce the complexity of things, lets examine the box drawing routines. Normally to display a nondestructive box on the screen you write something like this:

 old_row:=row()
 old_col:=col()
 old_cursor:=setcursor()
 old_screen:=savescreen(10, 20, 16, 59)
 old_color:=setcolor("w+/n, w+/r")
 @ 10, 20, 16, 59 BOX 'ÚÄ¿³ÙÄÀ³ '

Then when you are done and wish to remove the box, you reverse the procedure:

 restscreen(10,20,16,59,old_screen)
 setcolor(old_color)
 setcursor(old_cursor)
 setpos(old_row,old_col)

This scheme results in using 9 lines of code, 5 memory variables, and requires that the programmer maintain the box coordinates in 3 different places. After going through this procedure a few times I started wondering if there was a better way of doing this.

When I tried to solve this problem, I had several false starts. I created a procedure to display the box that saved all the variables to statics, and the next time it was called it would restore the box. That didn’t work too well since I often wanted more than a single box on the screen.

Then I tried saving the memvars to an array that I used like a stack. But that didn’t work out too well either, since it required that all boxes be removed in the same order that they were created.

Then I decided that all the memvars being used to store the box information belonged in the calling routine, where they had been all along. Despite the fact that this seemed to bring me back to square one I continued with this train of thought.

If I stored all the memvars being used by the box routine in an array, then all the memvars could be stored in a single package, and passed to the calling routine without complications:

aBox := CreateBox(10, 20, 16, 59, "w+/n, w+/r")

And when I no longer needed the box and wished to restore the original screen:

DestroyBox( aBox )

Please compile DEMO1, to see the basic box functions.

CreateBox() and DestroyBox() are used to replace 10 lines of code, and the array aBox was used to replace 5 variables. Putting all the data into the array aBox and handling only the array, makes things much simpler.

Now that we have developed this technique, we could theoretically create a number functions that work together, like CreateBox() and DestroyBox(), and use the data contained in aBox. And in the file BOX.PRG, I have a group of sample functions that do just that:

 CreateBox()
 DestroyBox()
 BoldBox()
 MoveBox()

Another benefit of this technique, is that we can have multiple arrays that each correspond to separate boxes, and use them all at the same time.

For example, we could write a program with a couple of boxes:

 aBox1 := CreateBox( 05,  26,  20,  53,  "w+/n, w+/n" )
 aBox2 := CreateBox( 10,  20,  16,  59,  "w+/b, w+/b" )

To move the second box:

MoveBox( aBox2,  -08,  -18 )

Then we could give the second box a highlight:

BoldBox( aBox2, "w+/b, w+/b" )

Then to remove both boxes:

 DestroyBox(aBox2)
 DestroyBox(aBox1)

This example does some fairly complex things, and it does so, in only six lines of code. To run this example, compile the file DEMO2.PRG.

This programming technique has a name, it is called Object Oriented Programing (OOP).

According to OOP terminology the arrays aBox1 and aBox2 are objects, and the functions CreateBox, MoveBox, BoldBox, and DestroyBox are methods.

Objects are collections of related data, or in dBase terminology, arrays of related memvars. In our example, aBox1 and aBox2 are qualify as objects since they contain related data (the coordinates of the box, the original color, cursor position, cursor status, etc.).

In object oriented programming, several instances of an object can be created, and later destroyed when we are finished with them. In our example, aBox1 and aBox2, constitute two separate instances of box objects.

If you look at the example in DEMO3.PRG, you will see that the program creates an array of four box objects, and four separate instances of the box object are on the screen at once.

A methods is a special type of function. Methods are functions that are grouped together, and manipulate the same data objects. In the file BOX.PRG, you will see the code for four methods that use the box objects ( CreateBox, DestroyBox, BoldBox, MoveBox )

CreateBox is a special type of method called a constructor, because it creates a box object and initializes it.

DestroyBox is a special type of method called a destructor, because it destroys a box object and frees of the memory that the box object used.

Every time we call a method / function, we pass it the object we want the method to manipulate. In our example, we have two objects, aBox1 and aBox2. To move the first box, we called the method MoveBox() like this:

MoveBox( aBox1, 1, 1 )

And to bold the second box, we called the method BoldBox() like this:

BoldBox( aBox2, "w+/b,w+/n" )

The constructor CreateBox doesn’t need to be passed the object, because the constructor creates the object.

Now that you understand what an object is, you can create additional functions / methods that use the box object. And hopefully go on to create your own objects and methods.

Cynthia Allingham, 1991

/***
*
* BOX.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Displays exploding box on the screen
* Returns: Previous screen contents
*/
FUNC CreateBox (nTop, nLeft, nBottom, nRight, box_color)
local save_window:=savescreen(nTop, nLeft, nBottom, nRight)
local save_color:=setcolor(box_color)
local save_cursor:=setcursor()
local save_row:=row()
local save_column:=col()
@ nTop,nLeft,nBottom,nRight BOX 'ÚÄ¿³ÙÄÀ³ '
RETURN {nTop, nLeft, nBottom, nRight, save_window,;
 save_color, save_cursor, save_row, save_column}
/***
* Written By: Cynthia Allingham 11/01/91
* Purpose: destroys the box and restores old settings
*/
FUNC DestroyBox (aList)
restscreen(aList[1],aList[2],aList[3],aList[4],aList[5])
setcolor(aList[6])
setcursor(aList[7])
setpos(aList[8],aList[9])
aList:=nil
return nil
/***
* Written By: Cynthia Allingham 11/01/91
* Purpose: Changes the box border
*/
FUNC BoldBox (aList, cColor)
@ aList[1],aList[2],aList[3],aList[4];
 BOX 'ÛßÛÛÛÜÛÛ' color cColor
return nil
/***
* Written By: Cynthia Allingham 11/01/91
* Purpose: Redimensions the screen
*/
FUNC MoveBox (aList, nVert, nHorz)
local save_window:=savescreen(aList[1],aList[2],aList[3],aList[4])
dispbegin()
restscreen(aList[1],aList[2],aList[3],aList[4],aList[5])
aList[3] += nVert; aList[1]+=nVert
aList[4] += nHorz; aList[2]+=nHorz
aList[5]:=savescreen(aList[1],aList[2],aList[3],aList[4])
restscreen(aList[1],aList[2],aList[3],aList[4],save_window)
dispend()
return nil
* EOF BOX.PRG
/***
*
* DEMO1.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Simple program demonstrating the creation and
* destruction of a box object.
*/
local aBox
set procedure to box
@ 00,00,24,79 box 'ÚÄ¿³ÙÄÀ³°'
aBox:=CreateBox(10, 20, 16, 59, "w+/n, w+/r")
@22,19 say padc("Press any key to destroy the box",40)
inkey(10)
DestroyBox(aBox)
* EOF DEMO1.PRG
/***
*
* DEMO2.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Demonstates the use of two box objects
*/
local aBox1, aBox2
set procedure to box
@ 00,00,24,79 box 'ÚÄ¿³ÙÄÀ³°'
aBox1:=CreateBox(05, 26, 20, 53, "w+/n, w+/n")
message("Press any key to create a second box")
aBox2:=CreateBox(10, 20, 16, 59, "w+/b, w+/b")
message("Press any key to move the second box")
MoveBox(aBox2, -08, -18)
message ("Press any key to bold the second box")
BoldBox(aBox2, "w+/b, w+/b")
message("Press any key to destroy both boxes")
DestroyBox(aBox2)
DestroyBox(aBox1)
func message(ctext)
@22,19 say padc(ctext,40)
inkey(10)
* EOF DEMO2.PRG
/***
*
* DEMO3.PRG
*
* Written By: Cynthia Allingham 11/01/91
* Purpose: Demonstates the use of four box objects
*/
local aBox[4]
local cnt
set procedure to box
@ 00,00,24,79 box 'ÚÄ¿³ÙÄÀ³°'
aBox[1]:=CreateBox(05, 05, 09, 30, "w+/n, w+/n")
@ 06, 07 say "box #1"
aBox[2]:=CreateBox(18, 03, 22, 14, "w+/b, w+/b")
@ 20, 05 say "box #2"
aBox[3]:=CreateBox(20, 48, 22, 77, "w+/r, w+/r")
@ 21, 50 say "box #3"
aBox[4]:=CreateBox(02, 64, 12, 75, "w+/gr, w+/gr")
@ 03, 66 say "box #4"
for cnt:=1 to 12
 inkey(0.5)
 MoveBox(aBox[1], +1, 0)
 MoveBox(aBox[2], 0,+4)
 MoveBox(aBox[3], -1, 0)
 MoveBox(aBox[4], 0,-4)
next
inkey(10)
for cnt:=1 to 4
 DestroyBox(aBox[cnt])
next
* EOF DEMO3.PRG

Variable Terms

Argument :

Generally, a value or variable supplied in a function or procedure call, or an operand supplied to an operator. In function and procedure calls, arguments are often referred to as actual parameters.

See also : Parameter

Constant :

The representation of an actual value. For example, .T. is a logical constant, string is a character constant, 21 is a numeric constant. There are no date and memo constants.

Declaration :

A statement used by the compiler to define a variable, procedure, or function identifier. The scope of the declaration is determined by the position of the declaration statement in the source file.

See Also: Identifier, Scope

Dynamic Scoping :

A method of determining an item’s existence or visibility based on the state of a program during execution. Example: A Clipper public variable may or may not be visible within a particular function, depending on whether the variable has been created and whether a previously called function has obscured it by creating a private variable with the same name.

See Also: Lexical Scoping, Scope

Field Variable :

A variable that refers to data in a database field, as opposed to data in memory.

See Also: Local Variable, Memory Variable, Variable

Filewide Declaration :

A variable declaration statement that has the scope of the entire source file. Filewide declarations are specified before the first procedure or function declaration in a program file and the program file must be compiled with the /N option.

See Also: Scope, Storage Class

Identifier :

A name that identifies a function, procedure, variable, constant or other named entity in a source program. In Clipper language, identifiers must begin with an alphabetic character and may contain alphabetic characters, numeric characters, and the underscore character.

Initialize :

To assign a starting value to a variable. If initialization is specified as part of a declaration or variable creation statement, the value to be assigned is called an initializer.

See Also: Assignment

Lexical Scoping :

A method of determining an item’s existence, visibility, or applicability (i.e., the item’s scope) by it’s position within the text of a program.

See Also: Local Variable, Scope, Static Variable

Lexically Scoped Variable :

A variable that is only accessible in a particular section of a program, where that section is defined using simple textual rules. For example, a local variable is only accessible within the procedure that declares it.

See Also: Dynamic Scoping, Local Variable, Static Variable

Lifetime of a Variable :

The period of time during which a variable retains its assigned value. The lifetime of a variable depends on its storage class.

See Also: Scope, Visibility

Local Variable :

A variable that exists and retains its value only as long as the procedure in which it is declared is active (i.e., until the procedure returns control to a higher-level procedure). Local variables are lexically scoped; they are accessible by name only within the procedure where they are declared.

See Also: Dynamic Scoping, Lexical Scoping, Static Variable

Memory Variable :

In general, a variable that resides in memory, as opposed to a database field variable. Sometimes used specifically to refer to variables of the MEMVAR storage class (private and public variables), as opposed to static or local variables.

See Also: Field Variable, Private Variable, Public Variable, Variable

Parameter :

A identifier that receives a value or reference passed to a procedure or user-defined function. A parameter is sometimes referred to as a formal parameter.

See Also: Activation, Argument, Function, Procedure, Reference

Private Variable :

A variable of the MEMVAR storage class. Private variables are created dynamically at runtime using the PRIVATE statement, and accessible within the creating procedure and any lower-level procedures unless obscured by another private variable with the same name.

See Also: Activation, Dynamic Scoping, Function, Public Variable

Public Variable :

A variable of the MEMVAR storage class. Public variables are created dynamically at runtime using the PUBLIC statement, and are accessible from any procedure at any level unless obscured by a private variable with the same name.

See Also: Activation, Dynamic Scoping, Function, Private Variable

Reference :

A special value that refers indirectly to a variable or array. If one variable contains a reference to a second variable (achieved by passing the second variable by reference in a function or procedure call), operations on the first variable (including assignment) are passed through to the second variable. If a variable contains a reference to an array, the elements of the array can be accessed by applying a subscript to the variable.

See Also: Array Reference, Parameter

Static Variable :

A variable that exists and retains its value for the duration of execution. Static variables are lexically scoped; they are only accessible within the procedure that declares them, unless they are declared as filewide, in which case they are accessible to any procedure in the source file that contains the declaration.

See Also: Dynamic Scoping, Lexical Scoping, Local Variable

Storage Class :

Defines the two characteristics of variables: lifetime and visibility.

See Also: Lifetime, Scope, Visibility

Variable :

An area of memory that contains a stored value. Also, the source code identifier that names a variable.

See Also: Local Variable, Private Variable, Static Variable

Visibility :

The set of conditions under which a variable is accessible by name. A variable’s visibility depends on its storage class.

See Also: Dynamic Scoping, Lexical Scoping