|
|
|
| The overall goal of style guidelines is to lower the maintenance costs of
software
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 conventions PROC Guidelines: Labels - all Labels must have a "C"omment on them. There should be no proc commands on the same line as the label. Buffer Values - be sure to document the contents of all of the buffer values at the top of the proc. With the following convention:
Access sentences - When composing an access sentence in a PROC place one statement per line instead of long statements on a single line.
Select List and Sort Statements: It is better to perform two different commands to do SSELECT and LIST. After the SSELECT statement a check should ALWAYS be done to make sure that the access command returned a select list. The 9997, 9998 and 9999 proc labels are reserved for this standard behavior of the procs. If a proc is allowed to produce a report to the terminal, then be sure to use the 9997 routine to stop after to last page and require the operator to hit return. Please follow the template below closely. USER.PREBILL.NOPOST PQN Hard Cording Values - Don't do it! If you need to modify a proc that has hardcoded values clean up the code to read like the following examples. This first sample code contains a hidden bug. When the company number is 004 or 005 then the program for 003 is run and the correct program is the same one as company 002. This was a real occurrence! The benefit of the latter sample (if you don't have time to really fix the program) is that the appearance of the code reflects the function of the proc. And if someone creates company 006 then nothing happens which is better than the wrong thing happening. Hardcoding values also makes program testing much more troublesome because each program must be checked for each company.
Select List and Sort Statements: It is better to perform two different commands to do SSELECT and LIST. After the SSELECT statement a check should ALWAYS be done to make sure that the access command returned a select list. The 9997, 9998 and 9999 proc labels are reserved for this standard behavior of the procs. If a proc is allowed to produce a report to the terminal, then be sure to use the 9997 routine to stop after to last page and require the operator to hit return. Please follow the template below closely. USER.PREBILL.NOPOST PQN C$ tpackert - 10:24:05 Oct 07 1997 C$ THE REPORT WIDTH IS 79 C C Copyright 1997, Avanti-Case Hoyt - all rights reserved C Portions Copyright Computer Business Associates/Vercom Software C C this proc is a template to be used when setting up new procs, this proc C prompts for the Terminal or printer option (question # 899) C it also waits for a carriage return if it is run to the terminal C it also executed the Select and List separately so that the proc C can check for the successful generation of a list with IF #S and jump C to label 9998 if no select list results C C C <<A1,CONO>> C <<A2,CONAME>> C <<A3,REPORT.DATE>> C <<A4,TORP>> C HGET.PROC.CONO P IF %1 = "END" GO 9999 MV %3 "PROGRESS BILLINGS REPORT" MV %4 "127,899" HREPORT.SCRN P IF %1 = "END" GO 9999 C C C IF %4 = "T" HTERM132 IF %4 = "P" HSET.132WIDE P HSSELECT INVOICE H WITH CONO = A"1 H BY CUST H BY JOB H BY INV.NUM H AND WITH TRANS.FLG = "PB" H AND WITH PB.PERIOD = "" STON H< P C IF #S GO 9997 C H LIST INVOICE H JOB H CUST H CUST.NAME H INV.NUM H INVOICE.DATE H TOTAL USER.TOTAL H ID-SUPP H HEADING "'C' A2 H'L' H'C'PROGRESS BILLINGS H 'C'AS OF A3 H 'L'" H FOOTING "PAGE 'P' - DATE 'D'" IF %4 = "P" H LPTR P 9996 C T (0,22),"Report is complete, press <enter> to continue" IP: GO 9998 9997 C T (0,22),"No items were selected, press <enter> to continue" IP: 9998 C IF %4 = "T" HTERM80 IF %4 = "P" HSET.132WIDE P 9999 C MV %1 "RETURN" HEASY.MENU P
Hard Cording Values - Don't do it! If you need to modify a proc that has hardcoded values clean up the code to read like the following examples. This first sample code contains a hidden bug. When the company number is 004 or 005 then the program for 003 is run and the correct program is the same one as company 002. This was a real occurrence! The benefit of the latter sample (if you don't have time to really fix the program) is that the appearance of the code reflects the function of the proc. And if someone creates company 006 then nothing happens which is better than the wrong thing happening. Hardcoding values also makes program testing much more troublesome because each program must be checked for each company.
Calling Basic from Dict Items - You can easily call basic programs from Dict items using Unidata. Generally you should avoid designing files that need to do things like this btu sometimes it just can not be helped because of the need to interface to a file in an unexpected way. When this happens a simple basic program called from the dict item, can save many hours of programming an entire report in Basic and will allow you to reuse the dict item in multiple reports. In the sales reports, there is the need to determine the SALES date of a job. Unfortunately, there is not a single sales date available due to the fact that there are multiple invoices and multiple invoice dates. For sales forcasting purposes, it is required to have a single date for all sales of a job. There is no single attribute where this data can be retrieved. It needs to be determined programmatically. In order to solve this, I created the subroutine in the DICT.BP file called COMM.SALES.DATE. This program determines the Sales date by finding the invoice date of the largest, non-pre-bill invoice. You can not use the most recent invoice date because jobs often have small invoices issued after the main "FINAL" invoice. The best guess for Sales period is the perion in which the largest invoice amount that is a non-prebill (ie invoice number does not end in "PB"). The rules for calling basic programs from dict items:
The DICT item in the COMMISSION file: :ED DICT COMMISSION COMM.SALES.DATE Top of "COMM.SALES.DATE" in "DICT COMMISSION", 6 lines, 66 characters. *--: P 001: I 002: SUBR('COMM.SALES.DATE',@RECORD,@ID) 003: D2/ 004: SALESēDATEē-------- 005: 8R 006: S Bottom. *--: The basic program that is Globally cataloged: The SALES.PERIOD parameter is the one passed back to the dictionary for processing. The second and third parameters are the first and second parameters from the DICT item (ie @RECORD and @ID). Note that the dict item @RECORD is a dynamic array. You can save parameter passing overhead, by passing specific attributes when the items get larger and only one or two attributes are needed by the subroutine. In this case, I only need attributes 15 and 18, but I got lazy. COMM.SALES.DATE SUBROUTINE COMM.SALES.DATE(SALES.PERIOD, COMM.ITEM, COMM.ID) * * * t packert - 3/10/98 * * this program is called from the COMMISSION file dict items, the purpose * is to determine a single date of sales. It does this by sorting the * invoice dates and totaling the amounts. The invoice date with the * largest amount invoiced is the sales date. This is used for the * sales forecasting information which needs a single invoice date for * considering the amounts as SALES. * SALES.PERIOD='' * * * first build an array of the invoice amounts totaled by date * AMT.ARR='' DATE.ARR='' MAX=DCOUNT(COMM.ITEM<14>,@VM) FOR INDX=1 TO MAX INVOICE.NUMBER=COMM.ITEM<14,INDX> IF INVOICE.NUMBER[LEN(INVOICE.NUMBER)-1,2] # "PB" THEN SALES.DATE=COMM.ITEM<15,INDX> LOCATE(SALES.DATE, DATE.ARR;POS;'AR') THEN AMT.ARR<POS> += COMM.ITEM<18,INDX> END ELSE DATE.ARR=INSERT(DATE.ARR,POS;SALES.DATE) AMT.ARR=INSERT(AMT.ARR,POS;COMM.ITEM<18,INDX>) END END NEXT INDX * * then find the date with the largest amount and use this date * NUM.PERIODS=DCOUNT(DATE.ARR,@AM) IF NUM.PERIODS=1 THEN SALES.PERIOD=DATE.ARR<1> END ELSE SALES.PERIOD=''; MAX.AMT=0 FOR INDX=1 TO NUM.PERIODS IF AMT.ARR<INDX> > MAX.AMT THEN SALES.PERIOD=DATE.ARR<INDX> MAX.AMT=AMT.ARR<INDX> END NEXT INDX END * RETURN *
The Sales reports also need to break out the components of costs and these are stores as specific values in a single attibute in the COMMISSION file. Attribute 23 of Commission file: (poor design - should be multiple attributes)
:ED DICT COMMISSION PAPER.COST.AMT AA.COST.AMT SEPARATION.COST.AMT D E . T R R P < 1 > Top of "PAPER.COST.AMT" in "DICT COMMISSION", 6 lines, 77 characters. *--: P 001: I 002: SUBR('COMM.PAPER.COST',@RECORD,@ID) 003: MR02, 004: PAPERēCOST AMTē------------ 005: 12R 006: S Bottom. *--: COMM.PAPER.COST SUBROUTINE COMM.PAPER.COST(PAPER.COST,COMM.ITEM, COMM.ID) * PAPER.COST=0 * MAX=DCOUNT(COMM.ITEM(23),@VM) FOR INDX=1 TO MAX PAPER.COST += COMM.ITEM<23,INDX,1> NEXT INDX * RETURN * < 2 > Top of "AA.COST.AMT" in "DICT COMMISSION", 6 lines, 71 characters. *--: P 001: I 002: SUBR('COMM.AA.COST',@RECORD,@ID) 003: MR02, 004: AAēCOST AMTē------------ 005: 12R 006: S Bottom. *--:
COMM.AA.COST SUBROUTINE COMM.AA.COST(AA.COST,COMM.ITEM, COMM.ID) * AA.COST=0 * MAX=DCOUNT(COMM.ITEM(23),@VM) FOR INDX=1 TO MAX AA.COST += COMM.ITEM<23,INDX,3> NEXT INDX * RETURN * < 3 > Top of "SEPARATION.COST.AMT" in "DICT COMMISSION", 6 lines, 79 characters. *--: P 001: I 002: SUBR('COMM.SEPRTN.COST',@RECORD,@ID) 003: MR02, 004: SEPRTNēCOST AMTē------------ 005: 12R 006: S Bottom. *--: COMM.SEPRTN.COST SUBROUTINE COMM.SEPRTN.COST(SEPRTN.COST,COMM.ITEM, COMM.ID) * SEPRTN.COST=0 * MAX=DCOUNT(COMM.ITEM(23),@VM) FOR INDX=1 TO MAX SEPRTN.COST += COMM.ITEM<23,INDX,2> NEXT INDX * RETURN * |