Model 204 coding guidelines: Difference between revisions

From m204wiki
Jump to navigation Jump to search
Line 1,026: Line 1,026:
<p>When sorting numeric fields, you must specify the NUMERIC attribute. Try to keep numeric fields under 64 significant digits. </p>
<p>When sorting numeric fields, you must specify the NUMERIC attribute. Try to keep numeric fields under 64 significant digits. </p>


<p>Technical Support recommends that you always specify whether you want to sort a field in ascending or descending order by specifying ASCENDING or DESCENDING in the SORT syntax for each fieldname. This helps make the code more explicit for future users. </p>
<p>It is recommended that you always specify whether you want to sort a field in ascending or descending order by specifying ASCENDING or DESCENDING in the SORT syntax for each fieldname. This helps make the code more explicit for future users. </p>


<p>You can sort records in found sets and on lists; however, records in a sorted set cannot then be placed on a list. Also, you cannot directly update sorted records; you must first perform a FIND or use the FOR RECORD NUMBER statement within a FOR loop. </p>
<p>You can sort records in found sets and on lists; however, records in a sorted set cannot then be placed on a list. Also, you cannot directly update sorted records; you must first perform a FIND or use the FOR RECORD NUMBER statement within a FOR loop. </p>

Revision as of 12:45, 11 September 2014

Introduction

Model 204 provides you with a versatile set of application development tools. This topic provides suggestions on how to use Model 204 features to create efficient applications.

Sample coding structure

The following table lists and describes elements of User Language code.

Sample coding structure

Element Usage
Comments General description of program function
Commands If not precompiled; for example, UTABLE and RESET. From V6.1 onwards, precompiled procedures can contain commands as well as code.
BEGIN
VARIABLES ARE UNDEFINED Require the declaration of all %variables
Declare %Variables Declare all %variables
ON Units Control errors, attention, locking conflicts; may be an INCLUDE statement
Screen definitions Define instream and included screens
Initialize %Variables Assign values, get globals
Dialog Control Read screen and input commands, process PF keys, get input data, perform edits, make audit entries
Record Selection Control Process Finds, Sorts, Lists
File Update Logic Store, change, delete, add, commit
Exit Logic Set globals, jump to start of current procedure, advance to next procedure, make audit entries
Subroutines Define instream subroutines
Includes Non-instream subroutines and screen definitions
END

Using comments

Use comments, but keep them concise. Comments must be preceded by at least one asterisk (*).

Note: Do not use symbols that can be misconstrued as dummy strings (??, ?&, ?S), even within comments. If you do, Model 204 treats them as dummy strings and gives you unexpected results. Also, do not end comments with a hyphen, because this also comments out the line following the comment.

The following formats for comments are suggested:

***************************************************** * INCLUDE VALIDATION SUBROUTINE FOR PART NUMBER * ***************************************************** IN PROCLIB INCLUDE SUB.PART.VAL

or:

/? INCLUDE VALIDATION SUBROUTINE FOR PART NUMBER ?/ IN PROCLIB INCLUDE SUB.PART.VAL

Using commands

When using commands within procedures:

  • Try to confine commands to the subsystem initialization procedure.
  • Set UTABLE parameters in the login procedure, especially if most procedures require the same general server size. Change individual procedures as needed, but be sure to reset the parameters. Use the TIME REQUEST command to obtain suggested values for table settings to help optimize the performance.
  • Keep track of any system-level parameters that you set; that is, RESET MCPU, MBSCAN, ERMX. You can monitor system-level parameters from the audit trail.
  • If you change parameters before a program is executed, or before control is transferred to another subsystem, remember to change them back afterward (for example, UTABLE under the application subsystem facility). This helps avoid runtime execution errors.

Declaring %variables

Although Model 204 allows you to implicitly declare %variables, it is strongly recommended that you declare all variables as one section of code at the beginning of a procedure or standard subroutine. To ensure that all %variables are declared, use VARIABLES ARE UNDEFINED.

Use declaration statements to specify:

  • Type of %variable
  • %variable length and number of decimal places
  • Number of elements (array %variables)
  • Use of the NO FIELD SAVE feature (optional)

For calculations other than basic integer arithmetic, we recommend specifying numeric %variables as FLOAT.

When declaring array %variables, use index loops. Also, use the NO FIELD SAVE option whenever possible to save space and to indicate that the %variable will not be used as a field name. For example:

BEGIN VARIABLES ARE UNDEFINED %X IS FLOAT %NAME IS STRING LEN 20 ARRAY (10) NO FIELD SAVE SCREEN SELECT PROMPT 'NAME' LEN 10 INPUT NAME LEN 20 END SCREEN FOR %X FROM 1 TO 10 READ SCREEN SELECT %NAME (%X) = %SELECT:NAME END FOR END

Using $functions

The $functions that are supported by Model 204 are documented on the SOUL $functions and List of $functions wiki pages. Many other $functions are available through the User Group and other sources. Test unsupported $functions thoroughly before you use them.

Call $functions like $Date only once per request. All $functions incur some overhead, and because the date rarely changes during a run, it is more efficient to establish the value once and, if you need to reference the value again, place the result of the call in a %variable.

You can eliminate the need to use $Substr to retrieve the first n characters of a value by assigning the original value to a variable of the correct length. This method makes the code more difficult to read and update, but it saves CPU time. For example:

BEGIN VARIABLES ARE UNDEFINED %A IS STRING LEN 10 %B IS STRING LEN 2 /? First 2 bytes of %A ?/ %A = '1234567890' %B = %A /? Truncate to two bytes ?/ PRINT '%B = ' WITH %B END

Prints:

%B = 12

If a situation involves repeated calls to a User Language $function, you can save CPU time by resolving the $function to a %variable and then using the %variable. For example, recode the following statement:

IF $SUBSTR(MAKE,1,3) EQ 'FOR' OR $SUBSTR(MAKE,1,3) EQ 'COM' THEN ...

To:

%MAKE IS STRING LEN 3 %MAKE = $SUBSTR(MAKE,1,3) IF %MAKE EQ 'FOR' OR %MAKE EQ 'COM' THEN ...

This saves time both by referencing the $function, $Substr, and the field, MAKE, only once.

Using the IF statement

When using IF statements, always use the IF...THEN...END IF format. That is, always end the statement with an END IF, and always use the THEN. This makes the statement easier for other people to read and update.

Formatting IF statements

Format IF statements as follows:

IF condition THEN . . . END IF

Use consistent operator syntax, that is, do not mix symbols and abbreviations. For example:

IF AGE LT 25 AND WEATHER EQ 'RAINY' THEN

or

IF AGE < 25 AND WEATHER = 'RAINY' THEN

Indent the operational logic. For example:

IF %A EQ 'X' THEN PRINT %A END IF

Always use a continuation hyphen to segment compound conditionals. For example:

IF %A EQ 'X' AND - %B EQ 'Y' THEN %C = %A END IF

Keep your IF statements as simple as possible:

  • Avoid unnecessary statement label branching.
  • Avoid using negative logic.

Evaluating IF statements

The condition following the IF statement is always evaluated to zero or nonzero. Therefore, it is more efficient to use the expression IF %N rather than IF %N NE 0 and, conversely, IF NOT %N rather than IF %N = 0.

If you are evaluating a number of not equal (or equal) comparisons against a known set of values, (that is, IF %A NE 'A' and %A NE 'B'...), then use the following syntax. This uses about half the CPU time as well as reducing the amount of needed NTBL, QTBL, and VTBL:

IF NOT $ONEOF (%A, 'A/B/C/D','/') THEN...

Using %variables in IF statements

IF statement expressions are rendered more readable by using meaningful %variable names, label names, or $functions such as $ONEOF or $INDEX to describe the condition. For example, the sample IF statement in the previous section is clearer when written as:

IF NOT $ONEOF (%VALID.DATA, 'A/B/C/D','/) THEN...

If you have a complex condition that is used several times in a procedure, you can create a fixed %variable and refer to the entire condition under that %variable name. For example:

CTRECS: COUNT RECORDS IN FDRECS IF NOT COUNT IN CTRECS THEN PRINT 'No records found' END IF . . . ******************************** ** EVALUATE IF CONDITION * ******************************** %VALID.DATA = (COUNT IN CTRECS) AND (%FLD = 'VAL') IF %VALID.DATA THEN . . . IF %VALID.DATA AND %1 = 5 THEN . . .

Using the computed JUMP TO

If an IF statement is becoming too complex, think about using a computed JUMP TO statement. Consider the next two examples.

Using ELSEIF without the JUMP TO:

%OPTION = OPT IF %OPTION EQ '1' THEN JUMP TO OPT1 ELSEIF %OPTION EQ '2' THEN JUMP TO OPT2 ELSEIF %OPTION EQ '3' THEN JUMP TO OPT3 END IF OPT1: ... process option 1 OPT2: ... process option 2 OPT3: ... process option 3

Using the computed JUMP TO:

%OPTION = OPT JUMP TO (OPT1, OPT2, OPT3) %OPTION ********************************** ** INVALID OPTIONS FALL TO HERE ** ********************************** PRINT 'INVALID OPTION' JUMP TO CHECKED OPT1: ... process option 1 OPT2: ... process option 2 OPT3: ... process option 3 CHECKED: ... end of jump to

ELSE IF vs. ELSEIF

Model 204 uses both the ELSEIF and ELSE IF statements. The differences in use are as follows:

  • ELSE IF functions as a nested IF statement. Note that for ELSE IF, each IF requires an END IF statement. For example:

    IF A > B THEN A = A - 1 ELSE IF B > C THEN C = C + 1 END IF END IF

  • For ELSEIF, only one END IF is required. For example:

    IF A > B THEN A = A - 1 ELSEIF A < B THEN A = A + 1 ***************** ** ELSE A = B ** **************** END IF

Finding records efficiently

Use The Model 204 FIND statement to search for specified information in your database. In general, use the FIND on fields that are defined as KEY or:

NUMERIC RANGE
HASHED
SORT KEY (SFGES. SFLS)
ORDERED

If you must search on a non-key field, try to pair the FIND with a Boolean AND using a key field as the other search criterion. The key field search is performed first, which means (ideally) that there are fewer records in the set requiring a Table B search. This helps minimize Table B access. You might also want to consider redefining fields so that they meet the more efficient search criteria.

In the following example, NAME is non-key and RECTYPE is key. The RECTYPE = DRIVER records are evaluated first using the KEY indices; NAME is then ANDed by performing a Table B search on each record in the RECTYPE = DRIVER set.

FD NAME = JANSSEN AND RECTYPE = DRIVER

Think about other methods of shortening or eliminating Table B searches. Among them:

  • Keep the found set small if you are doing a scan.
  • When building a system, if you know that a field will be used to find sets of records, define the field as KEY or as one of the types listed above.
  • Like the non-key search, pair IS PRESENT with a Boolean AND and a key field when doing a FIND to minimize the number of records that require a sequential Table B search.
  • Limit non-key searches by setting the MBSCAN parameter.

Whenever possible, use the results of previous FIND statements. This eliminates repeating I/O and CPU processing time already done for the first FIND. The following statement shows how to access a previous FIND:

FIND ALL RECORDS IN label FOR WHICH fieldname = value

In situations where you perform FINDs using both %variables and constants, put the constant terms (that is, terms without %variables or %%variables) into a separate FIND that is only executed once. Let the %%variable FIND access the previously found set. For example:

BEGIN FD.MAKE: FIND ALL RECORDS WHERE MAKE = FORD END FIND **************************************** ** PROMPT FOR FIELDNAME =VALUE PAIR ** **************************************** %FIELD = $READ ('ENTER FIELDNAME:') %VALUE = $READ ('ENTER VALUE:') FD.VALUE: FIND ALL RECORDS IN FD.MAKE FOR WHICH -  %%FIELD = %VALUE END FIND FOR EACH RECORD IN FD.VALUE PRINT VIN END FOR END

Field attributes that affect the way a file is accessed during a FIND are described in the following table.

Field attributes affected by searches

Field Attribute Description
Non-KEY, non-ORDERED
  • Table A for field code validation (during compilation)
  • Table B direct file search
KEY
  • Table A for field code validation (during compilation)
  • Table C for information (hashed access)
  • If not single record entry Table D for list or bit map
ORDERED
  • Table A for field code validation (during compilation)
  • Table D or B for tree access

The chart below can help you use FIND efficiently when you are writing requests or creating fields. The FIND is performed on the value '12345'.

The codes are as follows:

  • B = Table B Search
  • C = Table C Index
  • R = Numeric Range Index
  • O = Ordered Index

Consult the following table.

FIND statement efficiencies

FIND syntax Key Key Ord Char Key Ord Num NKey Ord Char NKey Ord Num Key NR NKey NR
= C C C O O C B
IS (EQ) B B O B O R R
IS GE B B O B O R R
IS LE B B O B O R R
IS GT B B O B O R R
IS LT B B O B O R R
IS BEFORE B O B O B B B
IS AFTER B O B O B B B
IS NUM B B O B O R R
IS NUM GT B B O B O R R
IS NUM BEFORE B B O B O R R
IS NUM AFTER B B O B O R R
IS IN RANGE B B O B O R R
IS NUM IN RANGE B B O B O R R
IS BETWEEN B B O B O R R
IS NUM BETWEEN B B O B O R R
(IS) LIKE B O B O B B B
IS ALPHA B O B O B B B
IS ALPHA GT B O B O B B B
IS ALPHA BEFORE B O B O B B B
IS ALPHA AFTER B O B O B B B
IS ALPHA IN RANGE B O B O B B B
IS ALPHA BETWEEN B O B O B B B
IS PRESENT B B B B B B B

Committing records

The COMMIT statement ends updating, commits the updated information to the file, and allows a checkpoint. Commit your records at the end of completed transactions.

The two forms of the COMMIT statement are:

COMMIT Leaves lists and found sets of records intact. COMMIT can be used within a FOR EACH RECORD loop.
COMMIT RELEASE Releases all found sets of records. You must use COMMIT RELEASE outside of a FOR EACH RECORD loop.

If your site updates records frequently and in high volume, be sure that your requests commit records at regular intervals. (Depending on the size of the records, commit no less than every 500 records as a rule of thumb.)

Releasing records

The RELEASE statement relinquishes control of a found set of records.

Use RELEASE as soon as you no longer need a found set, sorted set, or list.

The two forms of the RELEASE statement are:

RELEASE RECORDS IN label Releases records in the found set at the specified statement label.
RELEASE ALL RECORDS Releases records in all found sets, sorted sets, and lists.

It is generally safer to use the RELEASE RECORDS IN label syntax to avoid inadvertently releasing records that you are still processing.

Using the IN clause

The IN clause explicitly identifies the current file or group that is being processed. The advantage of using the IN clause is that you can retain the procedure file as the default file. In addition, using the IN clause helps clarify User Language code by explicitly naming the file being processed.

Because the IN clause allows you to specifically name a file or group, it is recommended that you use this clause in:

  • FIND statements
  • INCLUDE statements
  • LIST statements
  • Commands
  • STORE RECORD statements
  • CLEAR LIST statements

Always use the IN clause when processing a multiple file application. For example:

OPEN DAILY OPENC CLIENTS password OPENC CLAIMS83 password BEGIN FD.CLAIM: IN CLAIMS83 FIND ALL RECORDS WHERE...

Using lists

The use of lists is recommended for the processing of inquiry and report procedures. Place the records on the list as soon as possible. If you are not updating records on the list, release the found set as soon as you have established the list using the RELEASE RECORDS statement.

Records placed on a list are not necessarily locked. Any lock associated with the records results from their original found set. Nor does placing records on a list unlock them (only a RELEASE RECORDS statement accomplishes this). Therefore, if you place records on a list and then release the found set, you can retain the list for future processing. In this case, however, you cannot guarantee that the records will remain unchanged between the FIND and any subsequent processing statements.

When processing multiple files, you must first clear the list using the IN filename clause to establish list context and to prevent invalid cross-reference errors. For example:

IN filename CLEAR LIST listname

Clear lists as soon as you are done processing them using one the following statements:

RELEASE ALL RECORDS To clear all lists
COMMIT RELEASE To clear all lists
CLEAR LIST listname To clear a specified list
RELEASE RECORDS ON listname To clear a specified list

Remove records from a list using:

REMOVE RECORDS IN label FROM LIST listname REMOVE RECORDS ON listname1 FROM listname2

Sorting records

When sorting records, keep the found set as small as possible. If you are sorting large sets of records, think about using a sorted file structure, ORDERED or FRV attribute, or even a sorting package.

When sorting numeric fields, you must specify the NUMERIC attribute. Try to keep numeric fields under 64 significant digits.

It is recommended that you always specify whether you want to sort a field in ascending or descending order by specifying ASCENDING or DESCENDING in the SORT syntax for each fieldname. This helps make the code more explicit for future users.

You can sort records in found sets and on lists; however, records in a sorted set cannot then be placed on a list. Also, you cannot directly update sorted records; you must first perform a FIND or use the FOR RECORD NUMBER statement within a FOR loop.

If you do want to update sorted records, you can use the SORT k RECORD KEYS statement to generate a set of records sorted by specified keys. Because these records contain record numbers, you can update records in this set using the FOR RECORD NUMBER statement. For more information, see the Rocket Model 204 SOUL wiki pages.

FOR processing begins with the current record in the sorted set.

Using subroutines

Use subroutines to separate frequently executed logic and to segment requests for better readability. Simple subroutines are the most efficient way to segment frequently executed code.

Use comments within a subroutine to explain the purpose and function of the subroutine, inputs and output, and to name the places from which it is called.

Declare the %variables specific to a subroutine before the subroutine statement to ease maintenance and readability.

To make the code easier to read and maintain, try not to nest subroutines; that is, do not call one subroutine from within another.

Similarly, avoid including procedures that include other procedures, because this makes locating all the source code virtually impossible. One exception to this is for sets of subroutines that are often included together; in this case, you can create a procedure that consists only of INCLUDE statements.

Sample subroutine structure

Subroutine Element Description
SUBROUTINE
SUB.name (parameters)
Names a complex subroutine
label: subroutine Names a simple subroutine
Comments Describes functions including the name(s) of the calling program(s)
Declare %variables Assigns %variables, gets globals
Process logic Specifies any User Language statements that are used by multiple requests
Exit logic Sets globals, controls %variables and results of computations, and so on
END SUBROUTINE Ends the subroutine

To make complex subroutines easier to read, place the subroutine name and each %variable name on a separate line (except for the CALL statement). Remember to end each continued line with a hyphen. For example:

SUBROUTINE BINARY.SEARCH ( - SRT.ARRAY IS STRING ARRAY (*) %VALUE IS STRING LEN 20 %SUBSCRIPT IS FIXED OUTPUT)

Using COMMON %variables In complex subroutines

COMMON %variables, if properly used, can save NTBL and VTBL space. When naming COMMON %variables, use the subroutine name in which the %variable is first declared before each COMMON declaration. This helps to ensure that the %variable is not used inadvertently in other routines. For example:

DECLARE %PRINT.RECORDS.NRECS IS FIXED COMMON

When using string %variables in complex subroutines, follow these rules:

  • For STRING SCALARS, always specify LEN rather than depending on the VLEN setting.
  • For STRING ARRAYS, do not specify LEN; the length of the calling routine is always used.
  • For arrays, if NO FIELD SAVE is specified on the formal parameter, then it must also be specified on the actual parameter.
  • When using screen or database variable name %variables, you must convey the context to the subroutine through referencing (see below).

Note: Screen and Image items can be used as input into complex subroutines, but they cannot be used for output.

Using dummy strings in subroutines

For nonapplication subsystem facility subroutines that contain file or group-specific statements such as FOR EACH RECORD, FOR EACH VALUE, or FIND, but where the file or group can vary, use dummy strings on any line where the filename is needed.

The following example shows mixed use of both ?? and ?& dummy strings to prompt for a filename and then use that file within the procedures. The use of dummy strings is governed by the SUB and PROMPT parameters. See the Rocket Model 204 SOUL wiki pages and the Rocket Model 204 Parameter wiki pages for more information about using dummy strings in procedures and for information about the SUB and PROMPT parameters.

This example consists of three procedures: SETNAME, which finds the filename; MAKELIST, which places the desired records on a list; and PRINT.REC, which prints the records.

The advantage of using dummy strings in this instance is that you can change both the file and the field value easily. (With modifications, this example can also allow for changes in the fieldname.)

*********************** ** PROCEDURE SETNAME ** *********************** BEGIN VARIABLES ARE UNDEFINED ********************************************************* ** SET GLOBAL TABLE WITH NAME OF FILE. IF FULL, QUIT ** ********************************************************* SETGFT: IF $SETG ('FILE','??FILENAME') OR $SETG ('CONTINUE','Y') THEN AUDIT 'GLOBAL TABLE IS FULL REQUEST STOPPED' /? Debug message ?/ PRINT 'GLOBAL TABLE IS FULL' PRINT 'YOUR REQUEST HAS BEEN STOPPED' ENDIF END MORE ************************************ ** IF IT PASSES, GO TO MAKELIST ** ************************************ IF CONTINUE = Y, MAKELIST ************************ ** PROCEDURE MAKELIST ** ************************ MORE DECLARE LIST NAME.LIST IN FILE $&FILE ********************** ** PROMPT FOR NAME ** ********************** FDNAME: IN $&FILE FD LASTNAME = ?$LASTNAME END FIND ***************************** ** PUT FOUND SET ON LIST ** ***************************** PLACE RECORDS IN FDNAME ON LIST NAME.LIST **************************************** ** CALL SUBROUTINE TO PRINT RECORDS ** **************************************** CALL PRINT.RECORDS (LIST NAME.LIST) INCLUDE PRINT.REC *************************** ** PROCEDURE PRINT.REC ** *************************** SUBROUTINE PRINT.RECORDS (LIST NAME.LIST IN FILE ?&FILE) FRPAI: FOR EACH RECORD ON LIST NAME.LIST PRINT ALL INFORMATION END FOR END SUBROUTINE PRINT.RECORDS END