A Philosophically Engineered Approach to the Processing of Parameters
April 18, 2012 Ted Holt
Note: The code accompanying this article is available for download here.
Too often we humans give little thought to what we do or why we do it, even though taking an organized approach to an activity has its advantages. In this article, I present one approach to the handling of parameters in programs and tell why I consider this a good way to process parameters.
Parameters are data that are supplied to a program in order to affect the way it behaves. For example, the ability to supply file names, member names, and various options to the Copy File (CPYF) command allows me to use one program to copy any file instead of having to write a new program each time I want to copy a file.
Through most of my programming career, I took the approach of not referencing a parameter until I needed it. I regret that I wrote many programs before I finally realized that there is a better way. I now advocate that each parameter be verified insofar as possible in the initialization stage.
Before the program gets to the business of doing the work it was commissioned to do, it goes through three steps:
These three tasks are not as distinct as they may seem. The first two steps perform some data verification in the process of testing that a parameter was passed. Step three adds any other tests that need to occur.
I use CL to illustrate this approach, but the principle applies equally to programs written in other languages.
Suppose a program defines three input parameters: customer number; cutoff date; and sort sequence.
pgm parm(&inCustNbr &inThruDate &inSortSeq) /* Parameters */ dcl &inCustNbr *dec (5 0) dcl &inThruDate *char 7 /* cyymmdd */ dcl &inSortSeq *char 1
All parameter names begin with “in”, my convention for input-only parameters. (I use “io” and “ou”, respectively, for input-output and output parameters.)
Customer number is a required parameter in this illustrative application, while cutoff date and sort sequence are optional. The cutoff date (here called &inThruDate) defaults to a single, left-adjusted asterisk, which indicates that the program is to use the job date. The default sort sequence is ‘1’, which tells the system to sort on a certain set of fields, irrelevant to this discussion.
To carry out these three tasks, I work from copies of the parameters.
/* Copies of parameters */ dcl &CustNbr *dec (5 0) dcl &ThruDate *char 7 dcl &SortSeq *char 1
The copies of input and input-output parameters must be copied into the copies when the program begins execution. The copies of input-output and output parameters must be copied to any corresponding passed parameters before control returns to the caller. Except for these two times, the program uses the parameter copies, not the parameters themselves.
I also use other working variables as needed.
/* Other variables */ dcl &BadParm *lgl dcl &TestDate *char 7
The program sets variable &BadParm to true if any supplied parameter cannot be copied to the copy variable or if any parameter has an invalid value.
I also use three messages from message file USRMSGF.
ADDMSGD MSGID(USR8101) + MSGF(SOMELIB/USRMSGF) + MSG('Missing parameter &1.') + SECLVL('Parameter &1 is required. + You must supply a value to this program.') + SEV(30) + FMT((*CHAR 10)) ADDMSGD MSGID(USR8102) + MSGF(SOMELIB/USRMSGF) + MSG('Invalid value for parameter &1.') + SECLVL('The value supplied to parameter &1 is invalid.') + SEV(30) + FMT((*CHAR 10)) ADDMSGD MSGID(USR8109) + MSGF(SOMELIB/USRMSGF) + MSG('One or more parameters is in error. The program is canceled.') + SECLVL('Supply appropriate values for the parameters.') + SEV(40)
Now consider the three steps in detail.
Step one is to verify that all required parameters were passed to the program. The program does this by attempting to copy the required parameters to the copies.
/* 1. Required parameters */ /* .... Customer number */ chgvar &CustNbr &inCustNbr monmsg mch3601 exec(do) sndpgmmsg msgid(usr8101) msgf(usrmsgf) msgdta(CUSTNBR) + msgtype(*diag) chgvar &BadParm '1' enddo monmsg (mch0000 cpf0000) exec(do) sndpgmmsg msgid(usr8102) msgf(usrmsgf) msgdta(CUSTNBR) + msgtype(*diag) chgvar &BadParm '1' enddo
If the caller did not pass at least one parameter to the program, the CHGVAR command causes the system to send escape message MCH3601. The MONMSG command catches the exception and sends a diagnostic message to the caller, telling it which required parameter was not supplied and sets logical variable &BadParm to true.
The second MONMSG command catches all other errors, the only likely one of which is that the parameter’s value could not be converted to a five-digit packed decimal value. The program sends a diagnostic message and sets logical variable &BadParm to true.
Step two is to copy optional parameters to the copy variables. This step differs from the code for required parameters in one way. If a parameter is not supplied, the copy variable is loaded with a default value. Here’s the code for the cutoff date.
/* 2. Optional parameters */ /* .... Cutoff date */ chgvar &ThruDate &inThruDate monmsg mch3601 exec(do) chgvar &ThruDate '*' /* default = use current date */ enddo monmsg (cpf0000 mch0000) exec(do) sndpgmmsg msgid(usr8102) msgf(usrmsgf) msgdta(THRUDATE) + msgtype(*diag) chgvar &BadParm '1' enddo
If the caller passed two or more parameters, the program copies &inThruDate to &ThruDate. Otherwise, &ThruDate gets a single asterisk, which tells the program to use the job date.
After step two is complete, all parameters have been copied into the copy variables. From this point on, use only the copies, not the parameters themselves, until control returns to the caller.
The third step is to validate the values of all parameter copies. Here’s the validation code for cutoff date.
/* From this point on, use the copies of the */ /* parameters and not the parameters themselves. */ /* 3. Validate parameter values */ /* .... Cutoff date */ if (&ThruDate *eq '*') do rtvjoba cymddate(&ThruDate) enddo cvtdat date(&ThruDate) tovar(&TestDate) + fromfmt(*cymd) tofmt(*cymd) tosep(*none) monmsg cpf0555 exec(do) sndpgmmsg msgid(usr8102) msgf(usrmsgf) msgdta(THRUDATE) + msgtype(*diag) chgvar &BadParm '1' enddo
If the Convert Date (CVTDAT) command fails, the value of &ThruDate is invalid.
When the three steps are complete, variable &BadParm will be true if any error was found. In this case, the program sends an escape message to cancel itself.
if &BadParm then(goto Abend) . . . more code, executed if no bad parms . . . Abend: sndpgmmsg msgid(usr8109) msgf(usrmsgf) msgtype(*escape)
But if no parameter errors were found, the program continues to the task it was designed to do.
call pgm1 parm(&CustNbr) call pgm2 parm(&CustNbr &ThruDate) call pgm3 parm(&SortSeq) call pgm4 parm(&CustNbr) /* normal ending */ return
Notice that the “meat” of the program uses the copy variables, not the parameters themselves.
To see the program in its entirety, see the attached text file.
What I most like about this parameter-processing approach is that many common errors are found before the job has had an opportunity to corrupt the database or waste processing time. It is possible that one of the called programs may have a problem with the value of a parameter, but the likelihood of that happening is reduced.