Advantageous Options
July 9, 2014 Ted Holt
It happens to all of us. Someone tells us he needs a program just like an existing one, but with one small change. We clone the program, modify the clone, and now instead of one program to maintain when business requirements change, we have two. Before we know it, we have eight or 10 or more. Fortunately, there is a simple but seldom-used technique we can often use to avoid cloning programs. The technique to which I refer is to use an option parameter. An option parameter controls the behavior of a program. Contrast option parameters to data parameters, which contain data values, such as account numbers and dates. To find examples of option parameters, we need look no further than the IBM i operating system. Many CL commands have option parameters. For example: CRTBNDRPG . . . OPTION(*XREF *SHOWCPY *NOEXPDDS) The OPTION parameter gives us an easy way to control the behavior of the RPG compiler. Here’s another one: CPYF . . . FMTOPT(*MAP *DROP) The Format Option (FMTOPT) parameter provides a way for Copy File (CPYF) to copy different types of database files. If you choose to look further, you’ll see option parameters in many places within computing. Unix and Unix-like shells, such as Qshell, heavily use option parameters, which people sometimes call switches. Here’s an example: ls -lS The ls utility lists the contents of a directory. The l (lowercase ell) option tells the program to use the long format (one file per line). The uppercase S option tells the program to include the CCSID in the output. You can format options any way you like. I generally follow the convention of the system with which I’m working. In the case of IBM i applications, this means lists of values separated by spaces, except that I don’t feel compelled to include leading asterisks on the option values. I’ll illustrate with a CL command. You don’t have to create commands to use options; you can call programs directly. But the command interface does make it easier to pass lists of options to programs. Here’s the source code for CL command AR100, an accounts receivable application. Notice the third parameter, especially the VALUES keyword. CMD PROMPT('Post Open AR Transactions') PARM KWD(FROMDATE) TYPE(*DATE) DFT(*FIRST) + SPCVAL((*FIRST 000000)) PROMPT('Beginning + date') PARM KWD(THRUDATE) TYPE(*DATE) DFT(*LAST) + SPCVAL((*LAST 999999)) PROMPT('Ending + date') PARM KWD(OPTIONS) TYPE(*CHAR) LEN(8) RSTD(*YES) + VALUES(REPORT NOREPORT UPDATE NOUPDATE) + MAX(2) CHOICE('(NO)REPORT, (NO)UPDATE') + PROMPT('Options') This program does two things: it produces a report showing how the database is to be updated, and it updates the database. The options allow us to run the report in many ways. Ignoring the date parameters, here are some of those ways: AR100 AR100 OPTIONS(REPORT) AR100 OPTIONS(UPDATE) AR100 OPTIONS(REPORT UPDATE) AR100 OPTIONS(REPORT NOUPDATE) AR100 OPTIONS(NOREPORT UPDATE) AR100 OPTIONS(NOREPORT NOUPDATE) The last version is permissible, even though it makes no sense. The system passes the list of options to the command-processing program through a single parameter made up of a two-byte binary value and the options themselves. The binary value tells the number of options in the list. The command-processing program assigns default values to the options, then reads the list to change the option values. Here’s how it’s done in CL. pgm parm(&inFromDate &inThruDate &inOptions) dcl &inFromDate *char 7 dcl &inThruDate *char 7 dcl &inOptions *char 18 dcl &Ndx *uint 2 dcl &Option *char 8 dcl &OptOffset *uint 2 dcl &ToReport *lgl dcl &ToUpdate *lgl callsubr SetOptions if &ToReport do . . . enddo . . . if &ToUpdate do . . . enddo return subr SetOptions /* Set defaults for options */ chgvar &ToReport '1' chgvar &ToUpdate '0' /* Process the options */ chgvar &OptOffset 3 dofor var(&Ndx) from(1) to(%bin(&inOptions 1 2)) chgvar &Option %sst(&inOptions &OptOffset 8) select when (&Option *eq REPORT) then(chgvar &ToReport '1') when (&Option *eq NOREPORT) then(chgvar &ToReport '0') when (&Option *eq UPDATE) then(chgvar &ToUpdate '1') when (&Option *eq NOUPDATE) then(chgvar &ToUpdate '0') endselect chgvar &OptOffset (&OptOffset + 8) enddo endsubr endpgm Here’s the same thing in RPG: H dftactgrp(*no) actgrp(*caller) option(*srcstmt: *nodebugio) D OptionList_t ds qualified template D Count 5i 0 D Value 8a dim(2) D AR100R pr extpgm('AR100R') D inFromDate 7a const D inThruDate 7a const D inOptions const likeds(OptionList_t) D AR100R pi D inFromDate 7a const D inThruDate 7a const D inOptions const likeds(OptionList_t) D BuildReport s n inz(*on) D UpdateDatabase s n inz(*off) D Ndx s 3u 0 /free *inlr = *on; for Ndx = 1 to inOptions.Count; select; when inOptions.Value (Ndx) = 'REPORT'; BuildReport = *on; when inOptions.Value (Ndx) = 'NOREPORT'; BuildReport = *off; when inOptions.Value (Ndx) = 'UPDATE'; UpdateDatabase = *on; when inOptions.Value (Ndx) = 'NOUPDATE'; UpdateDatabase = *off; endsl; endfor; return; Of course, you don’t have to use this convention. Options can be anything you like. Later someone might ask for a summary report. You could add options SUM and NOSUM. Then someone might ask to see only the lines with errors. You might add an ERRONLY option. (Options don’t have to come in pairs.) Later you might decide to add report lines to aid in debugging, as I wrote about last year. Here’s the command source as it stands now: CMD PROMPT('Do an important AR task') PARM KWD(FROMDATE) TYPE(*DATE) DFT(*FIRST) + SPCVAL((*FIRST 000000)) PROMPT('Beginning date') PARM KWD(THRUDATE) TYPE(*DATE) DFT(*LAST) + SPCVAL((*LAST 999999)) PROMPT('Ending date') PARM KWD(OPTIONS) TYPE(*CHAR) LEN(8) RSTD(*YES) + VALUES(REPORT NOREPORT UPDATE NOUPDATE + SUM NOSUM ERRONLY DEBUG) + MAX(5) CHOICE('REPORT, UPDATE, ERRONLY ...') + PROMPT('Options') Here are the pertinent portions of the modified CL command-processing program: pgm parm(&inFromDate &inThruDate &inOptions) dcl &inFromDate *char 7 dcl &inThruDate *char 7 dcl &inOptions *char 42 dcl &Ndx *uint 2 dcl &Option *char 8 dcl &OptOffset *uint 2 dcl &ToReport *lgl dcl &ToUpdate *lgl dcl &Summary *lgl dcl &ErrorOnly *lgl dcl &Debugging *lgl callsubr SetOptions subr SetOptions /* Set defaults for options */ chgvar &ToReport '1' chgvar &ToUpdate '0' chgvar &Summary '0' chgvar &ErrorOnly '0' chgvar &Debugging '0' /* Process the options */ chgvar &OptOffset 3 dofor var(&Ndx) from(1) to(%bin(&inOptions 1 2)) chgvar &Option %sst(&inOptions &OptOffset 8) select when (&Option *eq REPORT) then(chgvar &ToReport '1') when (&Option *eq NOREPORT) then(chgvar &ToReport '0') when (&Option *eq UPDATE) then(chgvar &ToUpdate '1') when (&Option *eq NOUPDATE) then(chgvar &ToUpdate '0') when (&Option *eq SUM ) then(chgvar &Summary '1') when (&Option *eq NOSUM ) then(chgvar &Summary '0') when (&Option *eq ERRONLY ) then(chgvar &ErrorOnly '1') when (&Option *eq DEBUG ) then(chgvar &Debugging '1') endselect chgvar &OptOffset (&OptOffset + 8) enddo endsubr endpgm I encourage you, the next time you’re tempted to clone a program, to ask yourself whether adding an option parameter might not be a better choice. After all, you probably have better things to do than modify cloned programs. RELATED STORY Tracing Routines Explain Why The Computer Did What It Did
|