If you're still using IF/ELSE a lot, you need to get up to speed on IBM CL programming enhancements. Embrace SELECT, WHEN, and OTHERWISE.
Are you still coding with the CL IF and ELSE commands? I continue to do so in some cases, but I find that with the introduction of several new CL commands in V5R3, my use of IF/ELSE is way down. The commands that I am referring to are SELECT, WHEN, OTHERWISE, and ENDSELECT. This article will bring you up to date on these "new" programming constructs.
If your coding style is similar to mine, you probably use indentation in your program source to provide visual clues as to the flow of a program. As a very contrived example, if a CL program is utilizing a display file and you need to test for various command key situations (in addition to actually processing the display file input), you might in the past have initially used an approach such as this:
Pgm
DclF File(DSPF)
/* Do some processing */
SndRcvF RcdFmt(Prompt)
If Cond(&IN03) Then(Do)
/* Exit processing */
EndDo
Else Cmd(If Cond(&IN04) Then(Do))
/* Prompt processing */
EndDo
Else Cmd(If Cond(&IN05) Then(Do))
/* Refresh processing */
EndDo
Else Cmd(If Cond(&IN07) Then(Do))
/* Shift left processing */
EndDo
Else Cmd(If Cond(&IN08) Then(Do))
/* Shift right processing */
EndDo
Else Cmd(If Cond(&IN12) +
Then(Do))
/* Previous screen */
EndDo
Else Cmd( +
If Cond(*Not +
&IN25) +
Then(Do))
/* Not +
VLDCMDKEY +
processing */
EndDo
EndPgm
Unfortunately, with indentation, by the time you get around to actually processing any data, you find that you have room for perhaps one keyword per line, a condition that defeats our reason for indentation: to provide program source that's easy to read and understand. You most likely at this point resort to one of several workarounds:
- Not using indentation, making the source code not as readable/maintainable:
If Cond(&IN04) Then(Do)
/* Prompt processing */
Enddo
Else Cmd(If Cond(&IN05) Then(Do))
/* Refresh processing */
EndDo
Else Cmd(If Cond(&IN07) Then(Do))
/* Shift left processing */
EndDo
…
- Dropping all of the ELSE commands and inserting "dreaded" GOTO commands at the end of each IF DO group:
If Cond(&IN04) Then(Do)
/* Prompt processing */
GoTo CmdLbl(Done)
Enddo
If Cond(&IN05) Then(Do)
/* Refresh processing */
GoTo CmdLbl(Done)
Enddo
…
Done:
- Dropping all of the ELSE commands and simply falling through all of the IF checks:
If Cond(&IN04) Then(Do)
/* Prompt processing */
Enddo
If Cond(&IN05) Then(Do)
/* Refresh processing */
Enddo
…
- Leaving the indentation and ELSE commands in place but using GOTO (or, starting with V5R4, CALLSUBR) commands to move the processing to another location in the program. (The CALLSUBR option might be appropriate in any case if substantial processing is being done.)
- A variety of other workarounds
None of these workarounds, however, is ideal. A better solution is to use a different tool.
SELECT
The SELECT command is this tool. SELECT helps to address this visual aspect, along with another key item that we will get to shortly. The SELECT command (documentation can be found here) essentially starts a control structure to allow for processing based on mutually exclusive conditions…which happens to be what we were doing with the previous IF/ELSE command example. Using a SELECT approach, the previous code becomes this:
SndRcvF RcdFmt(Prompt)
Select
When Cond(&IN03) Then(Do)
/* Exit processing */
EndDo
When Cond(&IN04) Then(Do)
/* Prompt processing */
EndDo
When Cond(&IN05) Then(Do)
/* Refresh processing */
EndDo
When Cond(&IN07) Then(Do)
/* Shift left processing */
EndDo
When Cond(&IN08) Then(Do)
/* Shift right processing */
EndDo
When Cond(&IN12) Then(Do)
/* Previous screen processing */
EndDo
When Cond(*Not &IN25) Then(Do)
/* Not VLDCMDKEY processing */
EndDo
EndSelect
Quite an improvement to my way of thinking!
The SELECT command has no parameters. It defines the start of a sequence of one or more WHEN commands where the WHEN commands define the condition to be tested. When the condition is true, the command—or commands if the command is, for instance, a DO, DOFOR, IF, or another SELECT (you can nest up to 25 levels of SELECT groups)—associated with the WHEN command is run. After the command is run, control passes to the end of the SELECT group, which is defined by the ENDSELECT command. Though the conditions defined in the previous example are quite simple, you can have significantly more complex COND expressions. The expressions can use multiple variables, logical operators such as *AND and *OR, built-in functions such as %SST, %BIN, etc.
WHEN
The WHEN command, documented here, tests the logical expression defined by the condition (COND) parameter. If the expression is true, the command specified by the THEN parameter is run. Subsequent WHEN commands within the active SELECT group are bypassed. If the logical expression is false, the next WHEN command of the SELECT group is processed. The THEN parameter incidentally is optional. If the condition associated with a WHEN command is true and no THEN parameter is provided, control passes to the end of the SELECT group.
OTHERWISE
OTHERWISE is a very valuable command that can also be used within a SELECT group. This command, documented here, specifies the command (or group of commands) to run if none of the WHEN conditions are true. While this command is considered optional within a SELECT group from a compiler point of view, I (and many others) treat OTHERWISE as a required command from a programming standards point of view.
I use the OTHERWISE command in three ways. The least common is to only provide comments in the OTHERWISE block. These comments document my assumptions about the data that is not being processed in any way within the SELECT structure. I view this use of OTHERWISE as an opportunity to tell developers who may be looking at this code in the future (including me!) that I have given due thought to what is being let through.
With a slightly greater frequency, I use the OTHERWISE command to perform default processing on the data. I typically do this, though, only when the application processing is extremely well-defined. For instance, when parsing an array of character text for the presence of special control characters, I might use the WHEN command to check for the control character(s) and the OTHERWISE to handle the "no control character found" situation. In this case, the OTHERWISE block will be along the lines of moving the plain-text character to an output string.
My most common use of the OTHERWISE command is to trap errors of the type "How did I ever get here?" That is, after testing for all expected conditions using WHEN commands, OTHERWISE is used to capture the unexpected. The previous example using command/function key checks is perhaps on the trivial side, but it is possible that someone could add a new function key to the display file and forget to update the SELECT block (especially if the SELECT block is found in multiple locations of the program). More realistically, I'm sure you can think of situations where you are checking for various variable values and/or combinations of variable values and the safety net of OTHERWISE will come in handy. This is especially true when the variable values are coming from a database and the database field contents are provided by other programs. Testing for all combinations of A, B, C, and D is certainly doable when initially developing the application program, but what happens when E is added next year? Having the program automatically report that an unexpected condition—such as E—has been found certainly aids in problem determination (not to mention avoiding unintended processing, which may include database updates!). As I often write applications that are processing system-provided information (callable APIs, exit points, outfiles, etc.) or accessing external database files (a third-party database from my point of view, though it might be an in-house database from your perspective), I cannot know in advance what new values might be encountered when the system, or an application, is upgraded to a more recent release level. OTHERWISE aids me in handling the unexpected. Within my application, I generally have one generic message description that can be sent from the OTHERWISE block to identify the unexpected condition. The message includes the program name and provides a variety of replacement variable data types so that the message text can display the unexpected data values. One could provide this type of safety check within nested IF/ELSE logic by always ending the last IF with an unconditioned ELSE, but that's easy to forget. Declaring that every ENDSELECT is preceded by an OTHERWISE is much easier to remember and implement.
IF/ELSE?
At the start of this article, I mentioned that my use of IF/ELSE is way down. With the availability of SELECT structures, my use of IF/ELSE is pretty much limited today to logical variable (indicator) conditions. If the variable being examined has more than two states or possible values, I will generally use a SELECT, WHEN for each possible value and then use OTHERWISE and an error message for any other value. These error messages don't get used often, but when they do, they're a real time-saver! If you're interested in development productivity, I encourage you to keep the SELECT command in mind when you're doing future CL development and you come across a need for IF.
LATEST COMMENTS
MC Press Online