Using Fivewin
Author: Ran van den Boom
e-mail: r.vd.boom at casema.nl (replace at with @)
This Coding standard is developed
while working at Decos Software Engineering bv,
The Netherlands
www.decos.com;
www.decos.nl
Nowadays I work as software project manager, developing statistical software with teams varying from 2 to 20 team members.
Shortcuts: Table of contents, Code review checklist
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
| What | Where |
| The development environment | <add reference here> |
| The sources | <add reference here> |
The coding standard comprises two parts: the coding standard explained and a code review checklist.
I sometimes update this coding standard based on the reactions. You can send your comments to the email address above.
| Version | Date | Author | Modifications |
| 1.0 | June 29, 2000 | Ran van den Boom | Initial version, based on earlier coding standards |
| 1.1 | July 06, 2000 | Ran van den Boom | Reorganized entire document with new chapters on reviewing, added naming std for defines other than strings, statics and memvars, layout rules |
| 1.2 | August 23, 2000 | Ran van den Boom | Generalized the document so it can be used for all Clipper, Fivewin projects; added table of contents. |
| 1.3 | October 30, 2000 | Ran van den Boom | Incorporated
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.4 | August 27, 2001 | Ran van den Boom | Changed 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. |
| 1.5 | Sept 2003 | Ran van den Boom | Removed work link for I moved to Statistical Development Company |
| 1.6 | Sept 2005 | Ran van den Boom | Added comment from Vladimir Grigoriev on object constructors and Always use all parameters. |
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
| Variable | Prefix of variable | Example |
| Unknown | u | uParam |
| Character or string | c | cSearchStr |
| Array | a | aIndexes |
| Logical or boolean | l | lFound |
| Code block | b | bPreVal |
| File handle | f | fSourceFile |
| Numeric | n | nTotal |
| Pointer | p | Not 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 variable | Prefix of variable | Example |
| Static | s | saSearchSettings |
| Public, Private, Memvar | M-> or m | M->cDataPath or mcDataPath |
Strings
| Prefix | Define for | Example |
| 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
| Prefix | Type of numeric define | Examples |
| 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 id>_ | Array entries | #define SEARCH_NEXT 2 #define CVELD_NAAM 3 |
| M_ | Menu identifier | #define M_FILE_DOCS 12 #define M_SYS_INFO 78 |
| <Type id>_ | Identifier in CASE | #define DPP_CORPORATE 3 #define FLDS_ACTIVE 1 |
| <None specified> | Constants used locally | #define BTN_FONTHEIGHT 12 |
Other
| Prefix | Type of define | Examples |
| NIL_ | NIL | #define NIL_POSTBLOCK NIL |
| <None> | 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.
Use a prefix of the module or the class for a function name. So that its clear where the function can be found (module, class), where its 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 <module><object><action>. 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.
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 dont have general code layout, thats 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.
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.
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.
The source file should first contain the definitions (such as defines, xtranslates, xcommand, MEMVARs) that are located in other files, then local definitions.
The language defines look like:
#ifdef ENGLISH
...#define STR_HELLO "Hello"
#else
...#define STR_HELLO "Hallo"
#endif
/****************************************
<Project name>
<Description of source file>
Author(s) : <author(s)>
(C) Copyright <year>-<year>
<Company name>
****************************************/
The years behind Copyright are <year the project is started>-<current year the modification is made in>. For other projects similar file headers can be used.
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.
This chapter contains guidelines on how to structure the Clipper code.
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.
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 commas that were in the call. Therefore I suggest to use the function style using all parameters, and the examples in this coding standard adhere to that.
However there is some discussion on this guideline. Some argue (and they have a point there) that given the object oriented style of object oriented Clipper, the command style resembles closer to overloaded functions. In overloaded functions function A can be called with parameters A en B, but also with parameters A, B and C.
The important point here is to make a consistent choice: either use the command style or the function style, and either use all parameters or some. In my experience, un-experienced programmers tend to forget parameters. This suggests that experienced teams might feel more comfortable with the command style.
One of Clippers strong points is the possibility to use the parameters required and forget about the ones you dont 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 dont 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 instead.
Function returns BOOLEAN if return value is used and difference between .F. and .T. matters.
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.
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.
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 isnt, 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)
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.
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.
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.
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 dont 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.
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.
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+<any other text> if cTxt is not found.
This chapter contains miscellaneous considerations.
Since Clipper (in its 16-bit existence) has strong memory limitations (see heap and stack discussions on Phill Barnetts Oasis website and Wongs 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.
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.
In general, filters should not be used within the Clipper Fivewin environment. This is an extremely slow method. Better it is to:
For browses:
For searching:
This method is much faster than filtering, because you skip the item records that are not relevant. Using a filter will evaluate each record.
Be very careful on using SysRefresh. It gives Windows control over your application. Your program could be started in places where you dont expect it. If no user action is permitted for instance while your code is updating a database, prevent any user action and dont use SysRefresh(). If you dont 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)
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.
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.
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.
GROK can provide the following code checking capabilities (with respect to this coding standard):
| Code checked | GROK message id | GROK message |
| Detect unused local variables | X4003 | Local variable declared but never referenced |
| Detect unused local parameters | X4002 | Unreferenced formal parameter |
| Detect unused static variables | X4004 | Unreferenced static variable : 'lFileOpened' |
| Use procedure instead of function | X6010 | Function returns NIL |
| CASE statement error | X5003 | CASE/OTHERWISE clause has no body |
| CASE statement error | X6007 | DO CASE statement has no OTHERWISE clause |
| Intermediate return in function | X6002 | Procedure or function has multiple exit points : '<function name>' |
| Using variables before initializing them | X4001 | Variable referenced prior to assignment or initialization |
| Using variables before initializing them | X5009 | Variable not initialized prior to pass by reference : '<variable>' |
| Using variables before initializing them | X6011 | Possible reference prior to initialization |
| Using variables before initializing them | X6012 | Possible 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
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 stack fault.
B. Monitor statements in levels of severity
As an alternative you can also separate the Monitor statements in levels of severity. These are explained in Appendix Debugging using Monitor statements with levels.
In both cases it is good practice to mention the incoming parameters in the first Monitor statement, and the outgoing parameters and return parameter in the last Monitor statement.
The incoming Monitor statement starts with "> " followed by the function name and the incoming parameters.
The outgoing Monitor statement starts with "< " followed by the function name and the outgoing parameters.
It is not necessary to mention the function name since the Monitor statement itself can print it. Whatever choice is made: as long as it is done consistently: always mention the function name or never mention it.
To use the full capabilities in error detection the es2 Clipper compiler flag should be used. Any error or warning should be taken care of.
I will shortly describe the procedure to follow for reviewing.
The documents or code to review should contain
See also "The Seven Deadly Sins of Software
Reviews" by Karl
Wiegers:
http://msdn.microsoft.com/lubrary/periodic/period98/SDMag.htm
| Id | Check (Yes/No/N.a.) | Subject | Background | Reference in the coding standard |
| Design1 | Are the design specification and functional specification reviewed and accepted? | Without that, you cannot check for 110 % whether the code implements correctly the specifications | ||
| Design2 | Is the test specification ready? | Without it, you never know whether the bugs that dont come out of the review, will be found | ||
| Design3 | Is code according to design, or is design according to the code | |||
| Design4 | Does the code provide the functionality required | |||
| Design5 | Does the (modified) code affect other parts of the software as well? | The design and test spec should reflect all side-effects that could occur from modifying an existing part of the code | ||
| Design6 | Is it possible to use existing functions for parts of the new or modified functionality? | Structured programming | ||
| Design7 | Are there functions that are similar to the reviewed functions that can (and thus must) be used instead? | Structured programming | ||
| Design8 | Is it possible to write a function for a subpart of the function | Code readability, design improvement | Structured programming | |
| Design9 | Is the length of each function limited to 60 lines? | Code readability, design improvement | Structured programming | |
| Design10 | Are test functions written for new functions? | This
is better than testing! Improves code robustness |
||
| Design11 | Is
the function useable in other situations as well? If so, are those other cases tested? If not so, is other wrong usage prevented? |
Code robustness | Structured programming | |
| Design12 | Is the function in the correct file? | Structured programming | ||
| Design13 | Is multi-user usage checked? Each function that writes to the database should prevent that another user can read old data, modify it and overwrite other new data. | If multiple users can modify the same at the same time, errors will be the result. | ||
| Design14 | Is the layout of a dialog or window so that the user is forced to take logical steps to come to entering the data or to press the appropriate buttons? | Group the controls that are related. | ||
| Design15 | Is a Wizzard style used for complicated tasks? | So that the user gets the information in understandable parts. | ||
| Design16 | Is the number of controls not too high | About 20 controls is the maximum in which a browse or listbox counts for one control. Also possible is a form in which a maximum of 30 data entry fields are preceeded by 30 prompts. | ||
| Design17 | Is the dialog or window developed to support one task | If such a dialog or window would support multiple tasks, then it usually will confuse the user. Best is to make it into separate menu items or separate buttons. | ||
| Syntax1 | No intermediate returns in the code | Code will be more stable without it. GROK checks for this. Note older code may have intermediate returns in the code. | Structured programming, GROK | |
| Syntax2 | Are the calls to Fivewin functions all in the object function syntax or in the command line syntax | Use of command line syntax | ||
| Syntax3 | Are
all the parameters filled in either with the correct
value or with the default? If all other options are impossible, is NIL explicitly used? |
Always use all parameters | ||
| Syntax4 | Is each declaration on a separate line? | Coding style rules, naming conventions | ||
| Syntax5 | Are all includes in the source still required? | On modifying often unused include files are forgotten to remove | ||
| Syntax6 | Are all defines in the source code still required? | On modifying often unused defines are forgotten to remove | ||
| Syntax7 | Are unused variables removed? | On modifying often unused defines are forgotten to remove | ||
| Syntax8 | Is the FOR variable used after the NEXT in the FOR .. NEXT statement? | |||
| Syntax9 | Each CASE statement should have an OTHERWISE statement | Structured programming | ||
| Syntax10 | Are there no IF ELSEIF ENDIF used that should be replaced with CASE statements? | Structured programming | ||
| Syntax11 | Function returning NIL should be prevented; use a PROCEDURE in such case | GROK checks for this. | GROK | |
| Syntax12 | Are functions that are used only local STATIC FUNCTIONs? | Functions | ||
| Syntax13 | Is every variable used in a function initialized prior to usage? | Prevent bugs such as AADDing values to a non-initialized array. GROK checks for this. | GROK | |
| Syntax14 | Is each assignment or function call on a separate line? | Structured programming | ||
| Syntax15 | Have all functions, even the ones without parameters, () behind them? | Always use all parameters | ||
| Syntax16 | Assignments
use := Comparisons use == |
Be consistent; when comparison is misspelled as "=" then bugs will be the result. | ||
| Syntax17 | Comparisons with strings <> or != check the length of the strings or use !(.. == .. )? | Comparing strings | ||
| Syntax18 | Is the source file layout correct | Source file layout | ||
| Syntax19 | New strings are added as #define at the top of the file? | Source file layout | ||
| Syntax20 | String defines are at the top of the code, not inside the code? | In older code there are still constructions like #ifdef ENGLISH #else #endif inside functions | Source file layout | |
| Syntax21 | Has
each function provided it has a important functionality a
Monitor("> ") statement when entering and a Monitor("< ") statement on leaving the function |
To be able to debug the code after running | Debugging | |
| Syntax22 | Is unused code removed? Such as code with #ifdef NEEDED, or code which is commented out: // DoSomething(cParam) | Remove unused code | ||
| Syntax23 | Are defines used for expressions that are constant? | Constants, defines | ||
| Syntax24 | Is the return value of a function always used? | Code robustness | Error checking | |
| Syntax25 | Is AADD used with a check on array length? | Code robustness | Error checking | |
| Syntax26 | Is the use of SysRefresh correct (Fivewin only)? | Bugs prevented | Use of SysRefresh | |
| Syntax27 | Is the order of DBCOMMIT and DBUNLOCK correct? | Index corruption prevented | Order of DBCOMMIT and DBUNLOCK | |
| Syntax28 | Does DBSEEK use the correct seek expression? | Bug prevented | Other | |
| Syntax29 | Are statements like EXIT, LOOP, BEGIN SEQUENCE not used? | Structured programming | ||
| Syntax30 | Are declarations each on a separate line? | Structured programming | ||
| Syntax31 | Are Clipper commands replaced with Clipper functions? | Structured programming | ||
| Syntax32 | Are Clipper database commands prefixed with the alias? | Structured programming | ||
| Syntax33 | Are boundary situations within functions checked correctly? | Bounds checking | ||
| Syntax34 | Are boundary situations for variables checked correctly? | Bounds checking | ||
| Syntax35 | For functions is the use of global external variables reduced and are parameters used instead? | Functions | ||
| Syntax36 | Are global variables declared as STATICs and accessed through functions? | Memory usage | ||
| Syntax37 | Is the code compiled using the es2 switch? | Compiler settings | ||
| Names1 | Are button names and is button ordering consistent with other similar windows or dialogs | E.g. order should be Ok (or Close), New, Edit, Delete, Search, Print, Cancel | ||
| Names2 | Are memory variables such as LOCALs, PRIVATEs, PUBLICs, minimised? | Memory usage | ||
| Names3 | Are variables, function names, comment in readable English | |||
| Names4 | Are the function names according to the coding standard? | Usefull function names | ||
| Names5 | Is the function name unique and covers its functionality and distinguishes it from other function names? | Functions | ||
| Names6 | Functions start with prefix that indicates the function its origin or file location? | Functions | ||
| Names7 | Is each variable according to the naming schedule? | Variables | ||
| Names8 | Are the strings properly translated into Dutch and English? | |||
| Names9 | String defines are according to coding standard? | Constants, defines | ||
| Names10 | Are there two empty lines between each function? | |||
| Names11 | Are Clipper statements (IF .. ENDIF), commands (QUIT), functions (AADD(..)) uppercase? | Clipper in uppercase | ||
| Names12 | Is the indentation correct? Thus a constant number of spaces for each indented line? | Suggestion: use two spaces. | ||
| Names13 | No TAB characters used in the modified or new code? | Code more readable and easier to paste. Alternative is to use TABs always. | ||
| Names14 | Typing errors, variables always same case sensitivity | |||
| Names15 | Is a function that is conditional code surrounded by #ifdef x and #endif? | Code more maintainable | Other | |
| Comment1 | Is the code comment readable, is redundant comment removed, is there just enough comment, in the correct format? | |||
| Comment2 | Has
each function its header with function name and correct
description? Are the external variables that are used in the function mentioned? Are the function parameters excluded from the header but mentioned with their meaning? |
Only refer to the global variables that need explanation | Header of function | |
| Comment3 | Are all comments in the format /* */ | Note this can be whatever style preferred for other projects |
For instance:
STATIC FUNCTION
PrintSel(cMode)
...LOCAL aFilter
...LOCAL cUserVal := ALLTRIM(Decrypt(USERS->LOGIN,
ENCRYPT_KEY))
...IF cMode ==
"RA"
......aFilter := {"ADDRESS->EXISTING SELECTION"}
...ELSE
......IF cMode == "RP"
.........aFilter := {"DOCUMENT->EXISTING SELECTION"}
......ELSE
.........aFilter := {"FOLDER->EXISTING SELECTION"}
......ENDIF
...ENDIF
...RepAdmin(.F., aFilter,cUserVal)
RETURN NIL
Better:
/*=========================================================================*/
PrintSelDef
Print the selection definition.
/*=========================================================================*/
STATIC FUNCTION PrintSelDef(cMode)
...LOCAL aFilter
...LOCAL lOk
...Monitor("> PrintSelDef cMode
["+cMode+"]")
...lOk := .T.
...DO CASE
......CASE cMode == ADRSEL_SOORT
.........aFilter := {"ADDRESS->EXISTING SELECTION"}
......CASE cMode == POSTSEL_SOORT
.........aFilter := {"DOCUMENT->EXISTING SELECTION"}
......CASE cMode == DOSSEL_SOORT
.........aFilter := {"FOLDER->EXISTING SELECTION"}
......OTHERWISE
.........Monitor(" PrintSelDef mode unknown or not
implemented "+UtoC(cMode))
.........lOk := .F.
...ENDCASE
...IF lOk
......RepAdmin(.F., aFilter, ALLTRIM(Decrypt(USERS->LOGIN,
ENCRYPT_KEY)))
...ENDIF
...Monitor("< PrintSelDef "+LtoC(lOk))
RETURN lOk
Differences:
Appendix Debugging using Monitor statements without levels
Usage:
#include
"monitor.ch"
FUNCTION MyFunction(uParam)
...LOCAL lOk
...Monitor(">
MyFunction ["+UtoC(uParam)+"]")
...lOk := .F.
...IF uParam != NIL
......lOk := .T.
......Monitor(" MyFunction setting lOk to True")
...ENDIF
...Monitor("< MyFunction "+LtoC(lOk))
RETURN lOk
Compile sources with dMONITOR if you want to use the Monitor statements.
Monitor.ch:
#ifndef MONITOR
...#define Monitor(a)
#endif
Monitor.prg:
#ifdef MONITOR
...STATIC fDb := -1 /* debug file */
...STATIC nDebugVolgnr := 1
...STATIC nDebugMode := 1
...STATIC aFileHandles := {}
#endif
#ifdef MONITOR
/*===================================================================*/
GetDebugMode
nDebugMode := 0: don't output monitor statements
nDebugMode := 1: monitor statements as is
nDebugMode := 2: monitor statements with seconds
nDebugMode := 3: monitor statements with indentation
nDebugMode := 4: monitor statements with indentation and seconds
nDebugMode := 5: monitor statements with indentation and seconds
but without object functions containing ":"
Note: do not take monitor statements within this function!
/*===================================================================*/
FUNCTION GetDebugMode()
...LOCAL tObj
...LOCAL cIniPath
...LOCAL cDebugMode
...LOCAL nDebugMode
...cIniPath :=
GetWinpostIniPath()
...cDebugMode := ""
...tObj := tIni():New(cIniPath)
...tObj:Get(OPTION_SECTION, "DebugMode", "0",
@cDebugMode)
...nDebugMode := VAL(cDebugMode)
...IF nDebugMode>5.OR.nDebugMode<0
......nDebugMode := 0
...ENDIF
RETURN nDebugMode
/*===================================================================*/
OpenDebugFile
Opens the debug file DEBUG<nmbr>, until a debug file is
found,
nmbr <= 999 that does not exist and creates it. If all 999
files
exist then DEBUG000 is used and emptied.
/*===================================================================*/
STATIC FUNCTION
OpenDebugFile(cTxt)
...LOCAL cExePath
...cExePath := GetExePath ()
...nDebugVolgnr := 1
...IF (fDb == -1)
......fDb :=
FOPEN(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 2)
....../* Open een debug file dat nog niet bestaat, max 999 debug
files */
......WHILE (fDb != -1) .AND. nDebugVolgnr < 999
.........FCLOSE(fDb)
.........nDebugVolgnr++
.........fDb :=
FOPEN(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 2)
......END
......IF (fDb != -1)
.........FCLOSE(fDb)
.........nDebugVolgnr := 0
.........FERASE(cExePath+"DEBUG0")
......ENDIF
......fDb :=
FCREATE(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 0)
......IF fDb != -1
.........FWRITE(fDb,"DEBUG"+ALLTRIM(STR(nDebugVolgnr))
+ " " +;
..................DTOC(DATE())+"
"+TIME()+CHR(13)+CHR(10))
.........IF nDebugMode==2.OR.nDebugMode==4.OR.nDebugMode==5
............FWRITE(fDb,
PADR(PROCNAME(2),15)+"("+STR(PROCLINE(2),5)+")
"+STR(SECONDS())+;
........................" "+cTxt+CHR(13)+CHR(10))
.........ELSE
............FWRITE(fDb,
PADR(PROCNAME(2),15)+"("+STR(PROCLINE(2),5)+")
"+cTxt+CHR(13)+CHR(10))
.........ENDIF
.........FCLOSE(fDb)
......ENDIF
...ENDIF
RETURN fDb
/*===================================================================*/
Monitor
Writes text to the debug file.
If the debug file does not exist, the file is created.
/*===================================================================*/
FUNCTION Monitor(cTxt)
...LOCAL nNesting
...LOCAL cProcName
...LOCAL cExePath
...cExePath := GetExePath ()
...IF nDebugMode==NIL.OR.nDebugMode>0
......IF fDb == -1
........./* Track debug mode, from INI file: */
.........nDebugMode := GetDebugMode()
.........IF nDebugMode>0
............fDb := OpenDebugFile(cTxt)
.........ENDIF
......ELSE
.........fDb :=
FOPEN(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 1)
#ifdef MONITOR
.........IF fDb==-1
............SysRefresh()
............Error("Monitor could not open
DEBUG"+ALLTRIM(STR(nDebugVolgnr))+"; Error
"+NTRIM(FERROR()))
.........ENDIF
#endif
......ENDIF
...ENDIF
...IF nDebugMode>0
......IF fDb != -1
.........nNesting := 1
.........IF nDebugMode==3.OR.nDebugMode==4.OR.nDebugMode==5
............WHILE PADR(ALLTRIM(PROCNAME(nNesting)),30)!=
PADR("MAIN",30).AND.nNesting<250
...............nNesting++
............ENDDO
.........ENDIF
.........FSEEK(fDb, 0, 2) // set file pointer at end of file
.........IF nDebugMode==2.OR.nDebugMode==4.OR.nDebugMode==5
............cProcName := ALLTRIM(PROCNAME(1))
............IF
(nDebugMode==5.AND.AT(":",cProcName)==0).OR.nDebugMode!=5
...............FWRITE(fDb,
PADR(PROCNAME(1),15)+"("+STR(PROCLINE(1),5)+")"+;
.....................STR(SECONDS())+SPACE(nNesting)+cTxt+CHR(13)+CHR(10))
............ENDIF
.........ELSE
............FWRITE(fDb,
PADR(PROCNAME(1),15)+"("+STR(PROCLINE(1),5)+")"+;
..................SPACE(nNesting)+cTxt+CHR(13)+CHR(10))
.........ENDIF
.........FCLOSE(fDb)
......ENDIF
...ENDIF
RETURN .T.
/*=========================================================================*/
MonitorDatabases
Monitor the state of all opened databases.
/*=========================================================================*/
FUNCTION MonitorDatabases()
...LOCAL i
...LOCAL j
...IF EMPTY( ALIAS(1) )
......Monitor("No databases in use")
...ELSE
......Monitor("Databases")
......FOR i := 1 TO 255
.........IF !EMPTY( ALIAS( i ) )
............Monitor(STR( i, 3 ) + ": "+IF(SELECT()== i,
"=> ", " " )+ALIAS(i)+;
...................." current record
"+NTRIM((ALIAS(i))->(RECNO()))+;
...................." relation nr
"+NTRIM((ALIAS(i))->(DBRSELECT()))+;
...................." expr
"+(ALIAS(i))->(DBRELATION()))
............FOR j = 1 TO 15
...............IF !EMPTY((ALIAS(i))->(INDEXKEY(j)))
..................Monitor(IF((ALIAS(i))->(INDEXORD())==j,
"=> ", " " )+(ALIAS(i))->(INDEXKEY(j)))
...............ENDIF
............NEXT j
.........ENDIF
......NEXT i
...ENDIF
RETURN NIL
#endif
Appendix Debugging using Monitor statements with levels
Usage:
#define DBGMODE_ALL 3 /*
Monitor all */
#define DBGMODE_FLOW 2 /* Monitor warnings, errors and flow */
#define DBGMODE_WARN 1 /* Monitor warnings and errors */
#define DBGMODE_ERR 0 /* Monitor errors */
FUNCTION MyFunction(uParam)
...LOCAL lOk
...Monitor(DBGMODE_FLOW,
"> MyFunction ["+UtoC(uParam)+"]")
...lOk := .T.
...IF uParam == NIL
......lOk := .F.
......Monitor(DBGMODE_ERROR, " MyFunction Parameter must be
filled")
...ENDIF
...Monitor(DBGMODE_FLOW, "< MyFunction "+LtoC(lOk))
RETURN lOk
/* Debug file related */
STATIC fDb := -1
STATIC nDebugVolgnr := 1
STATIC nDebugMode := DBGMODE_ERR
/*=================================*/
Monitor functions
/*=================================*/
/*==================================================================================*/
Set debug mode
/*==================================================================================*/
FUNCTION
SetDebugMode(nNewDebugMode)
...nDebugMode := nNewDebugMode
RETURN .T.
/*==================================================================================*/
Get debug mode
/*==================================================================================*/
FUNCTION GetDebugMode()
RETURN nDebugMode
/*==================================================================================*/
Monitor(nDebugLevel,cTxt)
Calls for writing into debug file
/*==================================================================================*/
FUNCTION Monitor(nDebugLevel, cTxt)
...LOCAL cExePath
...LOCAL nNesting
...cExePath := GetExePath ()
.../* Check whether parameter list is correct */
...IF VALTYPE(nDebugLevel)=="C"
......MsgAlert("Error: Monitor called with wrong first
param: "+UtoC(nDebugLevel))
......cTxt := UtoC(nDebugLevel)
......nDebugLevel := DBGMODE_FLOW
...ENDIF
.../* Create/open debug file
*/
...IF nDebugLevel <= nDebugMode
......IF (fDb==-1)
.........fDb := OpenDebugFile(cTxt)
......ELSE
.........fDb :=
FOPEN(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 1)
......ENDIF
......IF (fDb!=-1)
.........nNesting := 1
.........WHILE PADR(ALLTRIM(PROCNAME(nNesting)),30)!=
PADR("MAIN",30).AND.nNesting<250
............nNesting++
.........ENDDO
.........FSEEK(fDb, 0, 2) // set file pointer at end of file
.........FWRITE(fDb,
PADR(PROCNAME(1),10)+"("+STR(PROCLINE(1),4)+")"+SPACE(nNesting)+cTxt+CHR(13)+CHR(10)
.........FCLOSE(fDb)
......ENDIF
...ENDIF
RETURN .T.
/*===================================================================================*/
OpenDebugFile
This function writes the given string into a debug file
/*===================================================================================*/
STATIC FUNCTION OpenDebugFile(cTxt)
...LOCAL cExePath
...cExePath := GetExePath ()
...nDebugVolgnr := 1
...IF (fDb == -1)
......fDb :=
FOPEN(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 2)
....../* Open een debug file dat nog niet bestaat, max 999 debug
files */
......WHILE (fDb != -1) .AND. nDebugVolgnr < 999
.........FCLOSE(fDb)
.........nDebugVolgnr++
.........fDb :=
FOPEN(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 2)
......END
......IF (fDb != -1)
.........FCLOSE(fDb)
.........nDebugVolgnr := 0
.........FERASE(cExePath+"DEBUG0")
......ENDIF
......fDb :=
FCREATE(cExePath+"DEBUG"+ALLTRIM(STR(nDebugVolgnr)), 0)
......IF fDb != -1
.........FWRITE(fDb,"DEBUG"+ALLTRIM(STR(nDebugVolgnr))
+ " " +;
....................DTOC(DATE())+"
"+TIME()+CHR(13)+CHR(10))
.........FWRITE(fDb,
PADR(PROCNAME(2),10)+"("+STR(PROCLINE(1),4)+")"+cTxt+CHR(13)+CHR(10))
.........FCLOSE(fDb)
......ENDIF
...ENDIF
RETURN fDb
IF <no other person is
active>.AND.FileLength(cDBTFile) > 30000000
...<Indicate the user to take action to decrease the DBT file
size, e.g. by copying the non-deleted records to a copy; note
that packing does not work>
ENDIF
/*=========================================================================*/
FileLength
Use only when cFile is not already in use, this will cause
serious shit
/*=========================================================================*/
FUNCTION FileLength(cFile)
...LOCAL nLength
...LOCAL nHandle
...Monitor(">
FileLength "+cFile)
...nLength := -1
...IF FileReadable(cFile)
......// Open the file read-only
......IF (nHandle := FOPEN(cFile)) >= 0
.........// Get length of the file
.........nLength := FSEEK(nHandle, 0, FS_END)
.........// Reset file position to beginning of file
.........FSEEK(nHandle, 0)
.........FCLOSE(nHandle)
......ELSE
.........Monitor(" FileLength File open
error:"+NTRIM(FERROR()))
......ENDIF
...ENDIF
...Monitor("< FileLength "+NTRIM(nLength))
RETURN nLength
/*=========================================================================*/
FileReadable
Check whether file exists and whether it can be opened for
reading.
Note the difference with the FILE function: when Access is
denied, FILE
considers the file existing, while FileReadable considers it
wrong.
/*=========================================================================*/
FUNCTION FileReadable ( cFile )
...LOCAL lResult
...LOCAL fTarget
...Monitor(">
FileReadable ["+cFile+"]")
...lResult := .F.
...fTarget := FOPEN(cFile, FO_READ+FO_SHARED)
...IF fTarget == -1
......lResult := .F.
...ELSE
......lResult := .T.
......FCLOSE(fTarget)
...ENDIF
...Monitor("< FileReadable "+LtoC(lResult))
RETURN lResult
/*=========================================================================*/
FILE
If file exist but cannot be read, it is considered to be
available.
/*=========================================================================*/
FUNCTION FILE(cFile)
...LOCAL lResult
...LOCAL fTarget
...Monitor("> FILE
["+cFile+"]")
...lResult := .F.
...fTarget := FOPEN(cFile, FO_READ+FO_SHARED)
...IF fTarget == -1
......lResult := .F.
......IF FERROR() == 5 /* Access denied */
.........lResult := .T.
......ENDIF
......Monitor(" FILE Ferror: "+ALLTRIM(STR(FERROR())))
...ELSE
......lResult := .T.
......FCLOSE(fTarget)
...ENDIF
...Monitor("< FILE "+LtoC(lResult))
RETURN lResult
Review of: <file>/<function>
Author: <name of author>
Date of review: <date>
Moderator: <moderator>
Attending: <person 1>, <person 2>, , <person n>
| Id | Document,
chapter, paragraph File, function |
Remark | Handled |
| 1 | |||
| 2 | |||
| 3 | |||
| 4 | |||
| 5 | |||
| 6 | |||
| 7 | |||
| 8 | |||
For links and other Clipper info:
For Fivewin:
http://www.FiveWin.Be.kz
http://www.fivetech.com
The last contains access to the Fivewin newsgroup on the Fivewin server
news.fivetech.com: local.fivewin.english.
See Coding standards for Java, C++, Delphi, Visual Basic and others.