Basic Guidelines
Home Up Photo Album Favorites

 

Home
Up

The overall goal of style guidelines is to lower the maintenance costs of software. Please do not succumb to the desire to put your mark on a program by using a different style. Any code style differences only drive UP the maintenance costs of the software. If you have a style convention then lets discuss it and include it in this formal style guide.

BASIC Guidelines:

Program Design - Programs should be designed to perform functions in a modular fashion. It is not considered good design to prompt for input, execute selections and run reports all within the same program. Generally, prompting for input should be done by a generic universal routine. In the best case, there should be a single INPUT statement/Routine for all programs to call, this ensures consistency. SELECTs should occur prior to a program being called and a report program should be relatively clear in function, it should produce a report from externally generated lists.

Using Scratch Files - You should avoid, creating scratch files for the purposes of running reports. If you need to do this them your design is probably flawed. If you need to do this, then make sure that your program creates properly sized files at least.

@VARIABLES - When you are doing screen handling with @(COL,ROW) commands, make sure you assign the values to a variable and reuse the variable. The @(-1) is a function as are all the @ commands and these have overhead. The system needs to look up the value of the escape sequences each time an @command is used. The @DATE, @TIME and @AM, @VM etc are also very efficient and should be used in place of DATE(), TIME() and equated values or CHAR() statements.

Comments - The use of copious comments is encouraged and required. If you make a change, to a program, please highlight the change with a comment that begins with "* <<avanti". The comment should include your initials and the date. All comments should be entered in lower case to help differentiate them from code. Notice that the BPLIST utility (in TOM.BP in rev 8 and 10 of Primac) automatically prints all comments in lower case. Commented out code should use the "!" comment indicator and true comments should use the "*" comment indicator.

Labels - All labels should be numeric, no alphanumeric labels are permitted. If a line begins with the program label then the line should contain ONLY a comment, there should be NO executable code on label lines. This is to enforce documentation. All labels are commented as well as all GOSUBs and GOTOs (if you dare).
GOSUB 1000 ;* process the main body item
.
.
.
1000 * process the main body item
.
.
.
RETURN

 

Format - Please use the FORMAT command in AE to indent your programs. Do not waste time indenting programs. To use FORMAT, just type FORMAT or FOR at any command prompt while in the editor. Command FORMAT -M0 -I2 will instruct the formatter to use a left margin of 0 and indent 2 spaces for each control structure.

 

Overall Style - The visual layout of the program should follow the function of the program. To this end, structured code should be used whenever possible. There should be only one exit point from any loop, internal subroutine, external subroutine or program. Do not use the GOTO, EXIT or CONTINUE flow control functions (unless absolutely required - ie rarely if ever)

Subroutine numbers: Please make sure that all subroutines appear in numeric order. Please be sure to follow the guidelines below:

0-999 - Don't bother with subroutine numbers this low

1000 - 9000 ; Main subroutine numbers group sub-sub routines into 1000, 1100, 1200 etc. so they indicate that they are closely related to each other.

10000 + ;* system type/ generic subroutines not user written

Variable Names - please make sure to use meaningful, consistent variable names, it is better to spell something out so that it reads well than to use an abbreviation that may not mean anything to someone else later. Variables that control LOOP structures should end with ".CNT". Variables that are part of a FOR/NEXT should begin with "INDX.". Variable names in Unidata should all be in upper case because this is a wide spread convention used in the Pick/Unidata market. When a variable is the result of a READ statement it should have a suffix of .ITEM when a variable is the result of a MATREAD then it should have a suffix of .REC. All item itds read and written should end with the suffix ".ID".

LOOPing - The following code segment is the style we wish to implement for loops used for item reads. All loops should 'fall through' and there should be NO gotos out of the loop. The use of IF/THEN/ELSE structures is encouraged.

Some programmers like to assign meaningless strings to variables in order to test for the first pass of a loop. Usually you will see things like PREV.PO.NO = "!@#$%" and then see tests for PREV.PO.NO = "!@#$%". This is not a good practice. Please use counts to indicate pass number and assign @AM to any PREV type variables. Any information extracted from a Fixed or dynamic array can never be equal to an attribute mark, therefore @AM is simpler and more reliable than "!@$%".

DONE=0
LOOP
   READNEXT SOME.ID ELSE DONE=1
UNTIL DONE DO
   GOSUB 1000 ;* some routine
REPEAT

 

FOR/NEXT loops - the FOR/NEXT loops should have a descriptive variable name beginning with "INDX.". Please do not use 1 or 2 character variable names. FOR/NEXT/WHILES and FOR/NEXT/UNTIL loops are acceptable. FOR/NEXT loops should 'fall through' and there should be no GOTOs out of the loop and the INDX variable should NOT be modified inside of the loop. The loop from and to values should be static values and not recalculated during the loop.

 

There should be no "FOR INDX = 1 TO DCOUNT(SOME.ARR,@AM)" constructs in the loops. Assign the lower and upper limit values to variable first or use constants then use this.

MAX = DCOUNT(SOME.ARR,@AM)
FOR INDX = 1 TO MAX
    GOSUB 1000 ;* some routine
NEXT INDX

 

Temporary files - Clean up after yourself. If you create a temporary file on the system to store a copy of the data prior to testing, please give it the name of the file plus the suffix of the date. If you make a copy of the EMPLOYEE file on June 6 1997, then create a file called EMPLOYEE.060697. Then in July make sure you go delete all of the old files that you no longer need. The better convention though is to create all files in separate account and then set a 'q' pointer to that account and file.

 

Global Catalog Space - Clean up after yourself. If you change the name of a program or no longer need a program that you wrote, please be sure to delete it from the global catalog space. Unless you do this regularly the global catalog collects too many obsolete programs which nobody needs or knows what they do. To catalog a program globally add the word FORCE to the end of the CATALOG BP PROGRAM FORCE command.

Dynamic Arrays - Generally it is easier to use dynamic arrays than DIMensioned arrays for item reads and writes. However, if you are reading or writing a PRIMAC file that has a CPYLIB item, use the CPYLIB item and a MATREAD. Do not read/update or write PRIMAC files using DYNAMIC arrays. If you use dynamic arrays, do not hard code numbers into the program. You should EQU the count to a meaningful variable name and use this as in this example. The prefix of the attribute number CONSTANT should be the same as the array it references and all attribute mark constants should have the suffix of ".AMC".

Note: Dimensioned arrays can be more efficient than dimensioned arrays when more than a few attributes are needed, however dimensioned arrays introduce an overhead to the runtime module because variable space needs to be layed out for each of the elements of the array. Generally it is better to use dynamic arrays for item reads and writes.

Instead of:
TJP.ARR<1>='TOM PACKERT'
do this:
EQU TJP.NAME.AMC TO 1
EQU TJP.ADDR.AMC TO 2
...
TJP.ARR<TJP.NAME.AMC>='TOM PACKERT'
 

Opening Files - When you open a file open it to a variable with the same name as the file OR to a variable that has the prefix "F." followed by the file name. Later, this makes it easier to find using, grep or ESEARCH, all of the programs that OPEN, READ, WRITE, DELETE and RELEASE items in the file. Whenever you open files or read control items that are required you MUST place and error handler in the ELSE clause. In production code, there is no excuse for not displaying an error message when an error occurs. The following is absolutely unacceptable (this was found in the payroll post program in REV 8):

 

OPEN '','SHIFT.STANDARD.HOURS' TO SHIFT.STANDARD.HOURS ELSE STOP

OCONVs with "T"ranslates - Don't do it! in basic programs - Open the file and read the item, don't cheat it costs us later.

Hard Coding Values - Don't do it! Avoid hardcoding behavior of a program in the program itself. Develop general rules and place an attribute in a control item. Do not create company or department specific variations of programs.

CLEAR - don't use the CLEAR statement. Initialize your own variables. It is important to know that you have not initialized a variable. The CLEAR command will keep variables unassigned message from appearing but also allows bugs to lay dormant longer causing more damage.

Read Statements - Please be sure to harden your read statements by using the READ THEN ELSE. Do not execute READs with the ELSE clause where the ELSE assigns the item to null. If the item SHOULD be there and is not, make sure you call the error routine.

Do not do this:

READ ITEM FROM SOME.FILE, ID ELSE ITEM=''
ITEM<1>='THIS IS A TEST'
WRITE ITEM ON SOME.FILE, ID
 

Instead use this hardened logic and do this consistantly:

READ ITEM FROM SOME.FILE, ID THEN
ITEM<1>='THIS IS A TEST'
 WRITE ITEM ON SOME.FILE, ID
END ELSE
 ITEM=''
 CALL PMC.ERROR('Item ':ID:' in SOME.FILE should have been found, but was      not')
END

 

Side Effects - Write your programs in such a way as to limit side effects. Limit your dependency on things being in the right place, it will save you time debugging problems later. For example, do not open "Q" pointers, you should always open the "F" or "DIR" pointer item in the voc and this should point to the real UNIX file name instead of a synonym file. If your program needs something to run, take the time to put these things there and then clean them up when you are done.

 

Do not do this:

DEPARTMENT
001: F
002: ../REV10B/DEPT
003: ../REV10B/D_DEPT

 

and then open the DEPARTMENT "Q" pointer in the program

OPEN '','DEPARTMENT' TO DEPARTMENT ELSE STOP '201','DEPARTMENT'

 

Item IDs - If you have the opportunity to design new files and structures, do not use fixed length constraints. Pick is a variable length system and these features should the used to provide additional flexibility in the future. For example if you were to design a new payables file, do not use fixed length company, vendor and invoice number definitions. You should use variable length fields with a delimiter like "*" or "!" or "|" and then use the FIELD string extraction functions of Pick.

Error Messages - Make sure that you offer the user meaningful error messages. An error message should always point the user exactly to the problem and instruct them on what to do to remedy the problem. You should not have to read the program source code to figure out how to remedy a problem. If you try to read an item from a file include the exact item id and file name in the error message.

EXECUTEs - If you are embedding an execute into a section of code, then you always need to check for the successful completion of the Execute. You should do this by using the RETURNING clause and checking for the existance of codes in the returned result codes. If you need an example look at the SP- programs found in the PRINTER.BP file. You should always check for result codes insteead of CAPTURED messages because the error message text can be changed by users or in multilingual environments where the error messages are in differnent languages.

Performance - Take pride in your code and make it as readable and as efficient as possible, However, since programmer time is so limited and valuable, make the program work FIRST and then later, if there is time, make it fast. Do not delay a feature because of performance concerns. This does not mean to write inefficient code and wait for free time, you should strive to make your programs run as fast as possible on the first try.

Variables - if you are going to use the results of a function more than once, then assign the results of the function to a variable and reuse the variable.

Files - items should be read and written once in a program, avoid multiple writes of the same item

Executes -

watch out for too many executes, an execute has a fair amount of overhead involved in it.

Temp Files/Work Files

don't create temporary files just to run reports, generally there is a better way

Subroutines

don't open files in subroutines

don't call subroutines from subroutines

Passing Variables - Don't ever pass common variables as parameters to subroutines. It is not a good practice and can even blow up some versions of Pick because the common space gets released when the subroutine is exited.

Pass By Value - If you want to pass a variable by value in Pick then surround the parameter in parentheses in the calling routine. This forces the run time engine to pass the results of the variable and prevents changes to the variable from returning to the calling routine.

This type of variable passing allows the changes in the subroutine to return to the calling routine:

CALL SOME.SUB( VAR1, VAR2)
SUBROUTINE SOME.SUB(VAR1, VAR2)
VAR1=''
VAR2=''
RETURN

this method will insulate the calling routine from the assignment statements in the subroutine

CALL SOME.SUB((VAR1),(VAR2))
SUBROUTINE SOME.SUB(VAR1, VAR2)
VAR1=''
VAR2=''
RETURN

 

Recursive programs - can be written in Pick Basic, but should be avoided unless you want to be the only person to maintain them. Many business oriented programmers have trouble understanding and debugging recursive programs, so you should avoid being clever and find a non-recursive way to do things.

 

Equated Dimensioned arrays - There is no benefit to assigning a value from a variable that is equated to an element of a dimensioned array to another variable, unless you really need a copy of the information contained in the variable. There is no performance overhead to variables equated to dimensioned arrays. Note that in Unidata the following IS valid: EQU SOME.VAR TO SOME.ARR<7> as well as the traditional EQU SOME.VAR TO SOME.REC(7).

Dates and Times - If a program uses dates and times then you should assign the date and time to a variable at the beginning of a program (unless you really need the time/date to be accurate like a time logging routine). This hardens the program against possible problems of running around midnight and saves some overhead in queriying the system date and time. Also you should ALWAYS use the internal date and time conventions. Do not store dates and times as formatted text, it will limit you flexibility later. When performing math on dates as in calculating due dates etc, always use the DD, DM and DY conversions. Do not use D2/ with fixed length or field extractions. This will harden your application further for Year 2000 and Euoipean conversions. When converting back to internal dates, you should further harden your code by checking to see what the date format is. Use a quick test like below. Remember, some systems store the date format as part of the terminal options and some systems store a system wide date format. If your application may span countries you should look for a system that allows dates to be formatted for the world on a terminal by terminal basis.

IF OCONV('0','D4/')='12/31/1967' THEN
* THIS IS A US DATE

END ELSE
IF OCONV('0','D4/') = '31/12/1967' THEN
* THIS IS EUROPEAN DATE
END ELSE
* DUH!
END
END

Date input defaults - When handling dates, please remember that SOME.DATE = ICONV('12/31/29','D2/') will be assumed to be 2029 and SOME.DATE = ICONV('01/01/30','D2/') will assume to be 1930. The current century assumption break point is 12/31/1929 and 01/01/2000. When the user enter the date as MM/YY/--29, Unidata will automatically assume 2000. When the user enters MM/YY/30-99, Unidata will assume 1900.

Totals stored in files - If you design a program that will store totals, make sure that your totals rebuild themselves automatically. The problem with stored totals is that they can get out of synch with the detail. There will always be problems with computers. Your programs should be designed to be self-correcting. For example: If you are keeping a year to date amount for a vendor, this total should be reacalculated from the detail each time. Otherwise, if you only add the current amount of an invoice then if the amount was already wrong, then result is still wrong. The overhead may seem unacceptable, but it is cheaper than programmer's time later, when, for some reason, you have the detail and summary out of synch. Generally though, you should avoid storing totals. Unidata allows you to write flexible dictionary items to calculate totals. A vendor's year to date amount is easily calculated from a multi-valued list of invoices with corresponding dates. There is no need to store a total vendor year to date amount.

Amounts stored in files. Amounts should always be stored in whole numbers without decimal points. If you are dealing with currency amounts then store all number in pennies.

Reserved words as variable names - Don't use reserved words like "DATA" as variable names. Unidata may allow a specific word but another implementation might not. We can not predict on which platform we may need to run on in the future.

Quick and Dirty programs - . If you write a "quick and dirty" program you should give this program a prefix of "QAD" to idicate it is such a program. This makes decataloguing QAD programs much easier and prevents accidental re-reruns. Note: you should also have potentially destructive QAD programs check for a run date in the program to allow themselves to only run on a single day. If every QAD has run date hard coded you may prevent some accidental damage to files in the future.

 

012: IF DATE() # ICONV('10/7/97','D2/') THEN
013: PRINT @(-1):
014: PRINT 'This quick and dirty program may only run on 10/7/97'
015: PRINT 'If you need to re-run this program then you must edit'
016: PRINT 'the program and recompile it with a new date. tjp'
017: STOP
018: END

 

Printer and Terminal settings - . If you are printing a report to the printer, do not assign the page length as a hard coded number. Read the TERM settings using the @LPTRHIGH and @LPTRWIDE functions. If you can avoid line counting then do it by using the HEADING and FOOTING options and let the system do the line counting for you.

Abstraction of Functions - When developing an application, you should avoid the hard coding of values. If you need to control the behavior of a code item, then you should place an unambigous option on a code item and lookup this value. For example, a taxable flag on an AP category should reside in the AP CATEGORY lookup file and have a value of Y/N/T for yes, taxable, no, not taxable and is a tax item itself. Your reports should lookup this value and react accordingly. You should not develop the application to contain hard coded AP CATEGORY codes and program the taxibility behavoir for each into the program. Byt abstracting the behavior one level you will avoid a lot of needless programming changes and allow the users greater flexibility in modiling their processes in the system.

File Design and Layout - Attributes should contain all like data and you should not encode values into specific value locations. There is nothing to be saved by placing something in VALUE 2 of an attribute instead of adding an attribute to the file. You should layout your files to be access friendly so that you can easily verify the results of any report in basic using a simple access sentence. It is important to cross check all report programs using access.

Aggregate files - If you have a file that contains summary information (aggregate) then you MUST design the rebuild program for this file up front. You should not build complicated files strucutures that do not self-heal or without utilities to rebuild them if needed. Your file design should record data at the smalled, unit record and this detail can be summarized from the unit record detail into summary files. You shoudl always verify against the unit record in order to ensure accuracy.

Counters and other assumptions - When you access a counter and will create an item on a file you MIUST always verify the accuracy of the counter. Do not allow your program to create invalid data by relying on the accuracy of a summary, counter. The counter is not a unit record. you need to verify that an id created by from a counter item, is not on file before allowing the program to continue