WinFakt! - (7759 of 826506 - 1%) 

 Home | Tips & Tricks | General Info | Downloads | NG Archive | Links | Email | Search | Register | Users | Login

 


 

 

FiveWin.info Poll
What is your primary used RDD
DBFNTX
DBFCDX
DBFMDX
DBFNDX
DBPX ( Paradox )
SIX ( ApolloRDD )
ADS ( Advantage )
SQL server
Other


View results so far
View old polls
 

 

 

General Info

 

Back to list

The Clipper language Coding standard

Coding standard for Clipper and Fivewin

Coding standard for Clipper

Using Fivewin

Author: Ran van den Boom
e-mail:
ran.van.den.boom@sioux.nl (work), R.vd.Boom@wolmail.nl (home)

Sioux Technical Software Development (in Dutch: Sioux Technische Software Ontwikkeling)
Rivium Oostlaan 47
2909 LL Capelle aan den IJssel
The Netherlands
www.sioux.nl

This Coding standard is developed while working at Decos Software Engineering bv
Bonnikeplein 24-26
2201 XA Noordwijk
The Netherlands
www.decos.com
www.decos.nl

Short-cuts: Table of contents, Code review checklist

Introduction

This coding standard should be used to improve the quality of the software product written in Clipper. Since each has his or hers own programming style, this coding standard should not be used to check this style, nor to check each character of the code. Instead, agree upon the issues that are relevant for your project. Together with the development team choose between the possible coding styles. Use the parts that are relevant given the situation the project is in.

Using this coding standard itself does not result in a good functioning program. Design is the first thing to do well. Without attempting to describe how to make a good design, I have added some indications of poor design. First however the naming conventions are described, and the way to structure the code itself.

This document is based on ten years of experience with Clipper and five years experience with Fivewin, and the experience of using a coding standard for VB, C and C++ projects. In all these projects deciding upon the coding standard is one thing, using it and reviewing code is essential. Therefore I have added several suggestions, a brief explanation on how to perform a review and a code review checklist. For a background of why this coding standard is developed, see Clipper coding standard background.

Aside from the coding standard for our projects we use a description of the development environment and a description of the files used, their contents and a global function explanation. Especially the list of general-purpose functions is very handy. Programmers that are new to the project tend to write their own functions instead of re-using the existing ones. For each project this coding standard is used, add a reference to the development environment description and the code description.

Table 1: Development environment and source description

WhatWhere
The development environment
The sources

Updates

The coding standard comprises two parts: the coding standard explained and a code review checklist.

I plan to update this coding standard based on the reactions. You can send your comments to R.vd.Boom@wolmail.nl.

VersionDateAuthorModifications
1.0June 29, 2000Ran van den BoomInitial version, based on earlier coding standards
1.1July 06, 2000Ran van den BoomReorganised entire document with new chapters on reviewing, added naming std for defines other than strings, statics and memvars, layout rules
1.2August 23, 2000Ran van den BoomGeneralised the document so it can be used for all Clipper, Fivewin projects; added table of contents.
1.3October 30, 2000Ran van den BoomIncorporated comments of Phill Barnett (1), (2), (3), (4), Douglas Woodrow
Added
background of this coding standard
Added links in last column of code review checklist
1.4August 27, 2001Ran van den BoomChanged link from http://Here.as/fivewin to http://www.FiveWin.Be.kz. Modified company I work for and my work e-mail addresses; I moved to Sioux from September 1, 2001 on.

Table of contents

Coding style rules, naming conventions
*
Naming scheme
*
Code readability
* Source file layout
*
Other layout guidelines
Code construction
*
Structured programming
*
Always use all parameters
*
Use defines
*
Use the command line syntax
*
Error checking
*
Useful functions and names
*
Button function names
*
Free resources as soon as possible
*
Adding functions
*
Static functions
*
Comparing strings
*
Order of DBCOMMIT and DBUNLOCK
*
Bounds checking
*
Other considerations
General coding suggestions
* Memory usage
*
Variables scope
*
Filters versus scope
*
Use of SysRefresh
* Maximum file size DBF and DBT
* File function
Enforcing the coding standard
*
Tools
*
How to review
*
Code review checklist
Appendix Example
Appendix Debugging using Monitor statements without levels
Appendix Debugging using Monitor statements with levels
Appendix: File functions
Remark list template
More information
* Clipper
*
Fivewin
*
Other coding standards

Coding style rules, naming conventions

Naming scheme

Variables

VariablePrefix of variableExample
UnknownuuParam
Character or stringccSearchStr
ArrayaaIndexes
Logical or booleanllFound
Code blockbbPreVal
File handleffSourceFile
NumericnnTotal
PointerpNot used

Only simple counters may use variables like i, n; however a more proper name should be used in new code.
This naming scheme looks somewhat like the Hungarian naming scheme for variables used in C.

Scope of variablePrefix of variableExample
StaticssaSearchSettings
Public, Private, MemvarM-> or mM->cDataPath or mcDataPath

Constants, defines

Strings

PrefixDefine forExample
BTN_Button text#define BTN_CANCEL "&Cancel"
PROMPT_Prompt #define PROMPT_CITY "City"
STR_String#define STR_ADDRESS "Address"
MSG_Message#define MSG_SEARCHNOTFOUND "Search string not found."
QUES_Question#define QUES_CONTATTOP "Do you want to continue at the top?"
WARN_Warning#define WARN_AREYOUSURE "Are you sure?"
ERR_Error#define ERR_FILEINUSE "Another user is using this file."

Numeric values

PrefixType of numeric defineExamples
LEN_String lengths#define LEN_PHONE 15
#define LEN_CLDESC 150
MAX_Boundaries maximum#define MAX_STRING_LEN 65535
MIN_Boundaries minimum#define MIN_DOCNUMBER 1
_Array entries#define SEARCH_NEXT 2
#define CVELD_NAAM 3
M_Menu identifier#define M_FILE_DOCS 12
#define M_SYS_INFO 78
_Identifier in CASE#define DPP_CORPORATE 3
#define FLDS_ACTIVE 1
Constants used locally#define BTN_FONTHEIGHT 12

Other

PrefixType of defineExamples
NIL_NIL#define NIL_POSTBLOCK NIL
Constant#define CRLF CHR(13)+CHR(10)

The NIL_ define is used to clarify NIL parameters in calls to Fivewin functions such as:
MsgWait( MSG_PROCESSINGDATA, MSG_WAIT, NIL_SECONDS )
This clearly shows what is the purpose of the third parameter.

Functions

Use a prefix of the module or the class for a function name. So that it’s clear where the function can be found (module, class), where it’s related to. Also the first character of each word within a function is written as a capital, the rest of the word is not in capitals. For instance:

search_next_string(cStr) /* Wrong */
SrchNextStr(cStr) /* Ok */

Since Clipper only uses the first ten characters of function names and names of variables, using long names is not useful. A function should be named . For example:

lOk := ObtainLinkWithAddressesAndAddressBooks(cAddressKey) /* Wrong */
lOk := AdrLinkGet(cAddressKey) /* Ok */

The documents in Table 1: Development environment and source description contain a description of the currently used prefixes.

Code readability

Why is code readability such an important issue in this coding standard? First of all if a team of programmers is working in the same source code files, you need to have an agreement on how the layout of the code should look like. Even if you agree to don’t have general code layout, that’s still an agreement. This coding standard has got a lot of agreements about coding style and you can decide per item to use it or not.

Second, the better the code is readable, the better can be maintained. This reduces the chance of bugs and simplifies debugging the code and fixing the bugs. In our experience there are a lot of bugs in the source code and not only in Microsoft products.

Remove unused code

Code indicated with #ifdef NEEDED or #ifdef OLD, or code commented out can be deleted. We make a backup with each release so if it was important code after all: blame the one who wrote the #ifdef or commented out the code. If you want to rewrite a function but the old code must be available for backward compatibility, rename the function by adding "Old" in front of the function name.

Also remove defines, includes, MEMVARs, etcetera if they are not used.

Clipper in uppercase

To distinguish Clipper statements from calls to own functions, uppercase Clipper statements, commands and functions are used. Note however that alternatives are also possible, as long as you can distinguish Clipper statements from other ones.
Example:
IF !EMPTY(cSrchString)
...AADD(aActiveFlds, cSrchString)
ENDIF

Fivewin internal code uses another scheme:
if ! ::lBorder
..::lBorder = .t.
..::Refresh()
endif

Yet another alternative is
If !Empty(cSrchString)
...Aadd(aActiveFlds, cSrchString)
EndIf

In all cases an editor with syntax highlighting such as Ed4Win proves to be very useful.

Source file layout

The source file should first contain the definitions (such as defines, xtranslates, xcommand, MEMVARs) that are located in other files, then local definitions.

  • Header
  • Include files
  • MEMVARs
  • STATICs
  • Defines
  • Language defines
  • Code

The language defines look like:

#ifdef ENGLISH
...#define STR_HELLO "Hello"
#else
...#define STR_HELLO "Hallo"
#endif

File header

/****************************************


Author(s) :
(C) Copyright -

****************************************/

The years behind Copyright are -. For other projects similar file headers can be used.

Header of function

Agree upon the template to use for function headers. Example of such a header:

/*=========================================================================*/
Name: SrchGetSrchColumns
Description: Return the columns to search in
Parameters in: cFileType - type of file (e.g. addresses, documents);
cFileKey - key of the file
Parameters out: array with the descriptions of the columns, empty array on error
Global variables used: none
/*=========================================================================*/

You should prevent to place information that is not relevant in the header. For example repeating the function name plus parameters does not add new information since the function is just below the header.

Other layout guidelines

  • Place .AND. and .OR. at the end of the line:
    IF aSetting[SHOW_WHEN_LOADING] .OR. ;
    ...aSetting[SHOW_AFTER_LOADING]

    is preferred over

    IF aSetting[SHOW_WHEN_LOADING];
    ....OR. aSetting[SHOW_AFTER_LOADING]
  • Behind each #endif, ENDIF, NEXT, END, ENDDO etcetera, repeat the loop criteria. For instance:
    ENDDO /* WHILE !EOF().AND.!lFound */
  • Use spaces before and after assignment operators:
    cKey := X->ADR_CODE
  • Either use or don’t use spaces before and after "(" and ")":
    cKey := BookKey(cBookName)
    Alternative:
    cKey := BookKey ( cBookName )
  • Add or do not add empty lines before and after Clipper statements:
    IF lOk
    ...lOk := SaveSelection(cUser, cDescription)
    ENDIF

    IF lOk
    ...oDlg:End()
    ENDIF

Code construction

This chapter contains guidelines on how to structure the Clipper code.

Structured programming

In Clipper there are a number of possibilities that will make your code less structured. And even if you do not use these commands, it is still possible to write an unstructured program. To reduce the risk of bugs that can result from unstructured programming, the following restrictions and guidelines are meant.

  • Use existing functions as far as possible. In our experience when (new) programmers start to work, first thing they do is write their own general purpose functions while the libraries and general purpose functions available should be used.
  • Don’t use intermediate returns in a function. It will make the risk of bugs higher, program flow is less clear and the code will be less maintainable. For instance, a function starts by opening two (database) files. Inside the function there are several error checks. On each error the function must return. Big chance closing the files will be forgotten (maybe not by the original programmer but possibly the next programmer isn’t that careful), unless there is only one exit point for the function. At that point the files are closed.
  • For the same reasons Clipper statements like EXIT, LOOP should also not be used. BEGIN SEQUENCE … END SEQUENCE is also not to be used.
  • Do not use IF ELSEIF ELSEIF ELSE ENDIF if a CASE statement is better. A CASE is better readable. In general a CASE is safer to use in such case because it can be extended easier. The number of IF..ELSEIF..ENDIF, FOR..NEXT, WHILE statements in one function is an indication for the quality of the design; the more of them there are, the worse the design. This is not always true; as said: it is an indication.
  • Each CASE statement should have an OTHERWISE statement. If the OTHERWISE should never occur then the OTHERWISE should be used to indicate an error. If the OTHERWISE can occur but nothing should be done then add that to the code in comment, so that it is clear when reading the code.
  • Each declaration should be on a separate line: this clearly shows how many variables are required. Variables that only occur in a subsection of the function suggest this subsection can be put in a separate function. Even if that new function is only used inside the function the code originated from it is still worthwhile: the code will be better structured, the scope of the variables is limited to their actual use, code will be better readable.
  • If the list of variables is very large, it suggests splitting up the function. The less variables are used, the clearer the code.
  • Each initialization and assignment should be on a separate line too. This makes the code more readable.
  • The ideal length of a function should be limited to about 60 lines. This number is chosen for then on a print the function will fit on a Letter or A4 size printout. This of course depends on the font size used. If the function length is larger, then it could be an indication the function is not well structured and that it must be split up. It could also be an indication the overall design of that part of the functionality should be rethought.
  • The number of parameters passed to a function is an indication of the quality of the design: the more parameters, the worse the design.
  • Each function that may return an error should be handled by testing on the possible error. If it occurs, logical lOk is set to .F., Monitor an error, if the user or supervisor can do something about the error show an error dialog with tips to solve the problem, or just show a message indicating why the action is aborted. The next code checks on lOk before continuing.
  • Prevent usage of Clipper commands, use functions instead (see also Always use all parameters). Reason is the function syntax shows the parameters used, and the functions can return a value or an error code.
    For example:
    RENAME TO
    should be replaced with
    nErr := FRENAME(,)
    because FRENAME returns nSuccess.
  • Use alias in front of database functions. Since it is not always clear what is the current alias, using the appropriate database alias will reduce the risk of hitting a "Error BASE/1003 Undefined variable" to zero. Disadvantage can be that it slows down the program a tiny bit.
    Example: Use
    CLIENTS->(DBSEEK(cSeek))
    instead of
    SELECT CLIENTS
    DBSEEK(cSeek)

Always use all parameters

In the past on adding parameters they were forgotten ("Hey, they are NIL anyhow, so why bother…") in some of the calls. Then later on, adding yet another parameter, we got a mix up and bugs resulted. Also NIL parameters were not filled in explicitly, and when one of the NIL parameters was replaced with another we got mixed up with all comma’s that were in the call.

One of Clippers strong points is the possibility to use the parameters required and forget about the ones you don’t need. In our experience that is a good thing for the Clipper functions themselves, but for our functions it is best to always supply all parameters, even if they don’t bare relevance in all situations in which case they are passed as NIL. Even better would be to define a NIL_PARAMETER value, to indicate more about the nature of the parameter that should be used in that place. For instance:

FUNCTION NAT(cSearchString, cInString, nTimes)

A function that returns the nTimes position of cSearchString in cInString, can be called with just the first two parameters, defaulting nTimes to one.

can be called by

nAt := NAT("ab", "abcdefgabcdefg")

Better would be:

#define NIL_TIMES NIL

nAt := NAT("ab", "abcdefgabcdefg", NIL_TIMES)

Best would be:

nAt := NAT("ab", "abcdefgabcdefg", 1).

Always use all parameters in calling a function. This is because if the parameter list is extended without adapting the other existing calls an error is easily made.

Function returns NIL if the return value is never used or not relevant. In such case, consider using a procedure instaed.

Function returns BOOLEAN if return value is used and difference between .F. and .T. matters.

Use defines

  • Use defines for strings. This makes it easier to write and maintain language specific versions. For example
    #ifdef ENGLISH
    ...#define MSG_SEARCHNOTFOUND "Search string not found"
    #else
    ...#define MSG_SEARCHNOTFOUND "Zoek tekst niet gevonden"
    #endif
    Alternative is to have a function that returns the string based on the current language setting. Also use defines in that case. For example:
    #define ID_MSG_SEARCHNOTFOUND 123
    MsgAlert(LangStr(ID_MSG_SEARCHNOTFOUND))
  • Use defines for expressions that are constant.
  • Use defines from include files if they are already available in include files.

Use the command line syntax

Because in our code we use a lot of defines to support both English and Dutch versions of the program, there is always the risk of a preprocessor overflow. Therefore we have created a new Fivewin.ch to include with lesser defines. This makes us bypass the preprocessor for calls like REDEFINE CHECKBOX ID …; we use TCheckBox():ReDefine(…).
Advantage is that it is clearer which parameters are used and there is less dependancy on include files.
Disadvantage is that if Fivewin modifies its API, we need to change all calls to the modified functions and the function has got a lot of empty or NIL parameters that are now explicitly shown.

Error checking

As much error checking as possible should be programmed. For each function that might return an error, or a non-expected value should check that. Handle errors even if they are not possible to occur, because in the future someone else might use your function the wrong way.

Such errors may not occur very often, but they sure help in debugging faster and future errors will be detected faster. Also external programs that might return an error like the ones started with ShellExecute: if it returns an error code, the code should handle the error.

When opening, creating, closing, removing files as much error checking as possible should be programmed. Use the functions that return an error instead of the commands that do not return an error. For instance: use FRENAME instead of RENAME.

NOTE: Maximum of array length is 4096. Whenever there is a change (however small) that an array can become larger, a test must be added. Note that the DIRECTORY function not always returns a valid array, so in case of using the DIRECTORY function the test on array length must be added for sure.

Useful functions and names

When writing a new function, think of a function name that is unique, covers the functionality of the function and distinguishes it from other function names. If it isn’t, a name might easily conflict with function names already in use or lead to confusion when determining which existing function can be used for a certain functionality.
aArray := GetArray(nType) /* Wrong */
aProcNames := ProcNames(nProcType) /* Ok */

Functions that do not add anything to the code must be removed. Example of such a function:
FUNCTION NumToStr(nVal)
RETURN STR(nVal)
Just use STR(nVal) instead of NumToStr(nVal)

Button function names

Functions used to start a button action start with a "Btn" prefix, to indicate the functions are called from a dialog or window.
Reason is functions like BtnDeleteAddress and DeleteAddress look to be doing the same. However the DeleteAddress function does the deletion and is also called from other functions besides BtnDeleteAddress.

Free resources as soon as possible

Always call free functions as soon as possible. Your memory requirements are reduced, and memory leaks are easier traced. See also Memory usage.

Adding functions

General purpose functions are placed in a number of source files. They are described in documents of Table 1: Development environment and source description. When writing new functions, look carefully where to add them. Combine new functions related to each other, like couplings to an external device in separate source files. Examples are DDE, Scanning, Fax, Word, Excel.

While doing a code review, check whether the functions are in the correct source file and whether the appropriate general purpose functions are used. Examples of such important functions are in Table 1: Development environment and source description.

Some of the source files are very large already, so if you want to add a function in there: think twice whether it should be in there and if so, whether the set of functions related could be part of a new PRG file. Also consider splitting up the source file that is (too) large.

Static functions

Functions should be declared STATIC if the scope of the functions is they are only used inside the file. Note that if a function is passed to an interface function, it can be that the function is no longer STATIC. When a function is part of an evaluation string it must be a global function.

Making functions non-static while they are only local used can cause conflicts with other similar named functions.

Comparing strings

NOTE: comparison of strings should be carefully written!

If the setting SET EXACT is set to OFF,
ALLTRIM("00131")==ALLTRIM("0013")
returns .T. while the texts are not the same. Everywhere in the code there is a risk the string comparison will return .T., use
PADR(ALLTRIM(cStr1),MAX_LEN)== PADR(ALLTRIM(cStr2),MAX_LEN)

MAX_LEN is to be determined based on the context. For instance when comparing ITEM->XINHOUD this test would become:
ITEM->XINHOUD == PADR(ALLTRIM(cStr2),LEN(ITEM->XINHOUD))

Comparison between two strings of fields of type character, the first one with spaces, the second without, gives the wrong result. So if two character fields need to be compared, put PADR around them (if SET EXACT OFF).
The advantage of SET EXACT OFF lies in seeking; if you seek for an address starting with "XYZ", seeking with SET EXACT OFF will find the first address with that characters. Of course provided there is an index on the address.

Order of DBCOMMIT and DBUNLOCK

Committing (DBCOMMIT) needs to be done before unlocking (DBUNLOCK), else another user can write a record or an index before the commit has finished! This results in index corruption, and explains why in test situations you will never get such an error while customers with a lot of concurrent users (so they surely don’t need the index corruption) do get this error when the order is incorrect

When appending a record, check on NETERR()! Don't forget to end a record modification with DBCOMMIT and DBUNLOCK. Else the record stays empty.

Bounds checking

The first and last situation of a loop, the lowest and the highest possible number, both are situations the programmer should be extra careful of. Often such cases are not checked at all for they will never occur. At least, that was what the original programmer might have thought.

The following boundary cases should be carefully reviewed in the code.

  • Functions: in FOR .. NEXT and WHILE .. ENDDO loops check the first and last situation in the loop. Are all the variables that are used within the loop correctly assigned, are the
  • Functions: while processing array elements, are all elements handled? Check the first and last element.
  • Functions: when parsing a string, is also the last part of the string handled correctly? For instance a string is separated by ";"
    cStr := ABC;DEF;GHI
    WHILE AT(";",cStr) > 0
    ...ProcessPart(SUBSTR(cStr, 1, AT(";",cStr)-1)
    ...cStr := SUBSTR(cStr, AT(";",cStr)+1)
    ENDDO
    /* Now GHI is forgotten to be processed */
  • Functions: in database searches, when the end-of-file is reached, is the last found record handled correctly?
  • Variables: the maximum string length is 64K (65535 characters); is there any chance this limit is exceeded?
  • Variables: using MEMOREAD is there a chance the file to be read is larger than 64K? If so, other functions should be used to read the file.
  • Variables: the maximum array length is 4096. Is there any chance this limit is reached?
  • Variables: the maximum numeric values to be expected, are they reached?
  • Variables: for numeric database fields, is there a chance they are filled with a value larger than indicated? For instance, a numeric database field has length 2, then the number 100 will give a "DBFNTX/1021 Data width error".

Other considerations

Conditional compilation, e.g. #ifdef CORPORAT: Put #ifdef .. and #endif around each function. Functions will be moved or renamed, and the test for defines might be mixed up if during moving a function the tests are forgotten (it has happened before).

Index files should have the extension NTX (when using DBFNTX that is).

Seeking: DBSEEK(ALLTRIM(cTxt)) will also find cTxt+ if cTxt is not found.

General coding suggestions

This chapter contains miscellaneous considerations.

Memory usage

Since Clipper (in its 16-bit existence) has strong memory limitations (see heap and stack discussions on Phill Barnetts Oasis website and Wong’s articles on this subject), also in this project the use of variables should be limited. In short: for the heap and stack and Eval stack only 640K is available.

See:
http://www.the-oasis.net/clipper-8.html
http://www.the-oasis.net/clipper-5.html

Therefore STATIC variables must be replaced by STATIC array entries, for with the separate entries are used with a define. This reduces the chance of an Unrecoverable Clipper Internal Error: 650 Processor Stack Fault meaning the stack is exhausted. Also Eval errors occur less frequently.

Class oGet uses the heap; if the heap is set too low then if controls are created ON INIT then they are not shown. Therefor in our code the use of creating controls from ON INIT is reduced. Even then you might run out of heapspace.

Several other actions have been undertaken to minimize the number of variables. When writing new code you should keep in mind that adding new STATICs, PUBLICs will decrease the memory available, while PRIVATEs and LOCALs will reduce the stack.

It is best to have a function that operates on variables passed to it then have a function that operates on global variables. This reduces memory overhead (the global variables are kept in memory all the time while parameters are placed on the stack as long as the function is active) and clarifies the code.

Even replacing PUBLICs with STATICs that are referenced through functions is better: the scope is better defines, and you can use an array for a set of STATICs, which will reduce memory usage.

The Memory Low error is a result of conventional memory being exhausted. Unfortunately, there is no function (as far as I know) that can tell you are running out of conventional memory. The function that indicates the conventional memory does not report the complete memory pool used for conventional memory. A solution might be to link in a Virtual Memory object that comes with the latest Fivewin. I have tested it and it does not get rid of the memory low error.

Variables scope

Using functions inside ORDCREATE and ORDCONDSET building an index all parameters of the function must be known at all times. So either use constants or the MEMVAR type. If you miss this, you get an hangup in DBSETORDER or ChangeOrder with or without a Clipper Internal Error DBCLOSEALL(0). Apparently Clipper tries to reactivate the function.

Codeblocks cannot use local variables. E.g.:
EditPostVal(aFld, { |aGet| Chk(aGet, aArr[nT]) } )
does not work if nT is a variable: the last known value of nT is used at the time Chk is being executed.

Alternative: make the Chk function retrieve the value based on aGet:Name or aGet:VarGet() and use an array in which a coupling is stored between the name of the variable in aGet and the value of aArr[nT]. Or use an evaluation &("Chk("+aArr[nT]+")". Note: aGet cannot be passed as parameter because it doesn't exist within the evaluation.

The next construction gives errors:
A user selects from a number of possible date fields.
cDateField := aDateFields[1] /* 5 characters, while aDateFields[2] is 10 */
EditFldAdd ( ..., "cDateField", aDateFields, EDIT_COMBORIGHT )
After the user has made a choice cDateField is still maximal 5 characters, while it should have been 10, if choice 2 was selected.

Filters versus scope

In general, filters should not be used within the Clipper Fivewin environment. This is an extremely slow method. Better it is to:

  • Set the order to the display order
  • Seek to the first occurrence

For browses:

  • Set the browse fields
  • Set the scope
  • Start browsing

For searching:

  • Skip until the record contents does not longer match the requirements

This method is much faster than filtering, because you skip the item records that are not relevant. Using a filter will evaluate each record.

Use of SysRefresh

Be very careful on using SysRefresh. It gives Windows control over your application. Your program could be started in places where you don’t expect it. If no user action is permitted for instance while your code is updating a database, prevent any user action and don’t use SysRefresh(). If you don’t prevent it, you can get very funny effects.

Example 1:

For instance, the menu can be activated before closing a window is ready. This may lead to bugs like: "Error BASE/1132 Bound error: array access" (the browse handle has become invalid), or "Alias X does not exist" (since the code opening the address file has not been executed correctly).

Example 2:

For instance if SysRefresh would have been used in the WaitInfo function, it would make the dialog non-modal and the purpose the user has to wait for the file to exist but no longer than nSecs is gone.

/*=========================================================================*/
WaitInfo
/*=========================================================================*/
FUNCTION WaitInfo(oDlg, cFile, nEndSecs)
...LOCAL nCurrSecs
...LOCAL nOldSecs

...nCurrSecs := SECONDS()
...DO WHILE !FILE(cFile) .AND. (nEndSecs==0 .OR. nCurrSecs < nEndSecs)
....../* Don't use SysRefresh() here for then the dialog is no longer modal */
......CursorWait()
......nOldSecs := nCurrSecs
......nCurrSecs := SECONDS()
......IF INT(nOldSecs) != INT(nCurrSecs)
.........oDlg:cMsg := cFile+" "+NTRIM(INT(nEndSecs-nCurrSecs))
.........oDlg:Say( 1, 0, xPadC( oDlg:cMsg, oDlg:nRight - oDlg:nLeft ))
......ENDIF
...ENDDO
RETURN NIL

/*=========================================================================*/
WaitInfoUntilExist
Wait until given cFile exists for no longer than nSecs seconds.
If nSecs < 0 then only do SysRefresh; if equals 0 don't check seconds.
Returns whether the file is there.
/*=========================================================================*/
FUNCTION WaitInfoUntilExist(cFile, nSecs)
...LOCAL nStartSecs

...Monitor("> WaitInfoUntilExist "+IF(cFile!=NIL,cFile,"NIL")+" "+IF(nSecs!=NIL,NTRIM(nSecs),"NIL"))
...IF nSecs < 0
......SysRefresh()
...ELSE
......IF nSecs==0
.........nStartSecs := 0
......ELSE
.........nStartSecs := SECONDS()
......ENDIF
....../* Set applications window to front because else MsgRun takes WinWord */
....../* or whatever is front as current active window parent */
......SetActiveWindow(GetWndApp())
......MsgRun(STR_WAITING, cFile, {|oDlg| WaitInfo(oDlg, cFile, nStartSecs+nSecs)})
...ENDIF
...Monitor("< WaitInfoUntilExist")
RETURN FILE(cFile)

Maximum file size DBF and DBT

If you expect that you can reach the maximum number of records in the DBF you should check for it. I guess the maximum is about one million (Clipper 5.2).

The maximum size of a DBT is 32MB. When the users of the product intensively use the product, a check is mandatory. A check is provided in Chapter 9 Appendix: File functions.

File function

Because checking for a file using FOPEN can return an error, new file functions have been written to work around this. See Appendix: File functions.

Enforcing the coding standard

To enforce the coding standard several tools, and debugging options are available. Also doing the review using the code review checklist will have great benefits.

Tools

GROK

GROK can provide the following code checking capabilities (with respect to this coding standard):

Code checkedGROK message idGROK message
Detect unused local variablesX4003 Local variable declared but never referenced
Detect unused local parametersX4002Unreferenced formal parameter
Detect unused static variablesX4004Unreferenced static variable : 'lFileOpened'
Use procedure instead of functionX6010Function returns NIL
CASE statement errorX5003CASE/OTHERWISE clause has no body
CASE statement errorX6007DO CASE statement has no OTHERWISE clause
Intermediate return in functionX6002Procedure or function has multiple exit points : ''
Using variables before initializing themX4001Variable referenced prior to assignment or initialization
Using variables before initializing themX5009Variable not initialized prior to pass by reference : ''
Using variables before initializing themX6011Possible reference prior to initialization
Using variables before initializing themX6012Possible pass by reference prior to initialization

Besides this, GROK has more handy features such as generating a function overview of a file, and displaying all defines and variables used for one source file.

See: www.prgtools.com, or contact John Kaster at Inprise.

Click! 2.03 Source Code Reformatter

To determine which functions are called by what and where, use this source code reformatter (well, it does more than that in fact).

For formatting Clipper source code, see: http://www.the-oasis.net/click00.htm

Debugging

In some cases CLD may be linked with the executable to debug the code.
In most of our projects we use debugging statements such as the one explained here.

A. Monitor statements without levels

These are displayed first and allow nesting and logging of seconds. See Appendix Debugging using Monitor statements without levels.

It allows display of seconds. This is required if you want to improve performance and need to know what the slow parts are. For example, RAID 5 systems are known to be slow in database transactions and virus scanners will also slow down creation of intermediate files like the ones needed on (re)indexing.

Also you can use nesting to be able to detect functions that go in too deep causing a Processor

 

 

 

 

© 2002 Bekz.net, Inc. All Rights Reserved.