|
CL-Like Error Handling in RPG
by Kevin Vandever
How many programs in your shop handle file errors? What about general program errors, such as divide by zero,
array index, or pointer exceptions? The tools are there to do so. The program and file information data structures
provide error information, and the *PSSR subroutine and %STATUS built-in function can be used to trap and
handle that information, but that isn't enough. Well, with V5R1, IBM has given RPG the capability to handle errors much the way CL does and,
in doing so, has kicked RPG up a few notches!
The Old Way
It's not impossible to handle errors in RPG. For example, if you open a file for update, when you CHAIN to that file,
you can easily interrogate whether that record is locked and act accordingly, as in the following code:
C
DoU %Status <> 1218
C CustomerID Chain(e) Customer
C EndDo
That's pretty simple stuff. If a record is locked (1218 status code), the program will try again until it is unlocked. If
any other file error is encountered, the program will not bomb, thanks to the error extender, e, on the CHAIN, but
you will not have control over the error, and so the program will continue to run under false pretenses, so to speak.
Of course, you could insert an error-handling subroutine either manually or by defining an information file
subroutine in your file spec, as below:
FCustomer UF A E K Disk InFSR(*PSSR)
Once you've defined the subroutine in the F-spec, you must write the *PSSR subroutine as you would any other
subroutine and then let it tell your program what to do with the errors it encounters. Now, any exception caused by
an operation to the CUSTOMER file--if that operation includes the error extender, e--will be trapped and
subsequently handled in your error routine, if you've written code to handle that error.
Well, that's great for the CUSTOMER file, although there are a lot of IFs or any other file you define with a File
Information Subroutine (INFSR) entry in the F-spec, but what about program errors? You can use the %STATUS
built-in function to trap those errors, but you have to specifically check the program status using the %STATUS
function after every operation you want to handle. No wonder we don't handle these errors very well.
The New Way
Have you ever noticed that most CL programs handle errors significantly better than RPG programs? Isn't that odd,
since it is the RPG programs that contain the most critical business rules, data access, and processing logic? CL
handles errors better, because, well, it's extremely easy to trap and handle errors in CL. Throw some Monitor
Message (MONMSG) statements around your code, and you're done, right? What if I told you that error trapping
and handling in RPG has recently become as easy as in CL? I thought so! That's how I reacted, too. With the
addition of three new op codes, you can now monitor for any program- or file-related error, in a consistent manner,
and handle it accordingly.
I have provided two example RPG programs: one in traditional coding style,
CSTMNT, and one in the new
free-form style, CSTMNTFREE; these files
are also available for download.
(For more information on free-form RPG, check out my article
"Let Your Hair Down With Free-Formed
C-Specs.")
MONITOR, ON-ERROR, and ENDMON
OK, not much to these programs. They accept some data and, depending on that data, update or write the
CUSTOMER file. But they do illustrate error handing. Three new operation codes were added to RPG in V5R1 to
perform kicked-up error handling: MONITOR, ON-ERROR, and ENDMON. Let's first look at the MONITOR
operation.
MONITOR
The MONITOR op code is used to begin error monitoring. Once the MONITOR operation is entered, the program
monitors all C-specifications between it and the ENDMON operation. When a program or file exception is
encountered on any statement within the monitor block, control is passed to the appropriate ON-ERROR operation,
and the logic within that ON-ERROR section is performed. If all the statements within the monitor block complete
successfully, control is then passed to the statement following the ENDMON operation.
ON-ERROR
The ON-ERROR operation acts much like the WHEN operation, the difference being that, when an exception is
encountered in your RPG program, control is immediately passed to the appropriate ON-ERROR statement; whereas
WHEN clauses are interrogated in order until the appropriate one is reached. Each ON-ERROR statement is
followed by one or more statements that will execute when that specific ON-ERROR block is triggered. The block is
ended when another ON-ERROR or the ENDMON statement is reached. As you can see by my code, I have trapped
for very specific errors as well as for more generic, catch-all errors. The first ON-ERROR statement traps a record
lock error. I have created a constant called RecordLock in my D-specs and set it to the status code for a record lock,
which is 01218. When I encounter a record lock, I simply display a message to the screen using the DSPLY
operation. The next ON-ERROR traps for *FILE--that is, any file-related exception not already trapped with another
ON-ERROR. File-related status codes range from 1000 to 9999, and they are all trapped with the ON-ERROR
*FILE statement. With these two statements, I am covered for any file-related exception that may arise. In my
example, I am telling the program to do something specific on a record lock but handle all other file exceptions the
same way. Depending on your requirements, you may choose to monitor for other specific file-related exceptions;
however, whatever you do, I recommend that you always insert an ON-ERROR *FILE statement for added
protection.
The third ON-ERROR statement traps a program exception that occurs when a result field isn't large enough. Again,
I have defined a constant called Result2Big to hold the actual status code, 00103. I do this to make my code more
readable for the next person who has to get in and marvel at it. Ha! Anyway, anytime this exception is raised within
the monitor block, the logic under this ON-ERROR statement will execute. This is where this enhancement really
starts to shine. To perform this type of program exception handling prior to V5R1 would have taken a lot more
effort.
The next ON-ERROR statement, ON-ERROR *PROGRAM, traps all program exceptions not being trapped by
other ON-ERROR operations. Any program exception within the monitor block will be trapped and handled by this
ON-ERROR block. Program status codes range from 00000 to 00999. Pretty nifty, eh? The last ON-ERROR
statement is the mother of all catch-alls. It is coded either ON-ERROR *ALL or simply ON-ERROR. *ALL, which
is the default. As you may imagine, this statement will trap any file or program exception that isn't already being
trapped. In my example, this code would never get executed, because I am already performing *FILE and
*PROGRAM error handling.
Last, I use the ENDMON operation to end error monitoring. Any C-specifications coded after the ENDMON
operation will not be trapped unless you start another monitor block with the MONITOR operation.
Cans and Cannots
For each monitor block, you must have at least one ON-ERROR operation. The ON-ERROR operations must be
coded in the same routine as the MONITOR and ENDMON operations. For example, you cannot code a MONITOR
and ENDMON in the mainline and place the ON-ERROR statements in a subroutine that is called within the
monitor block. I tried this one; it won't work.
Monitor blocks can be placed anywhere within the C-specs. You can embed monitor blocks within IF, DO,
SELECTs, and other monitor blocks. And IF, DO, and SELECT can be used with monitor blocks, as my code
illustrates. For monitor blocks that are embedded within other monitor blocks, the inner-most block is used first
when an exception arises. You can also place ON-ERROR statements anywhere within the monitor block, but,
where possible, you are probably better off to keep things modular. My preference is to follow a structure as
follows:
MONITOR
Program logic
ON-ERROR block
ENDMON
Level indicators can be used with the MONITOR operation to indicate that the MONITOR operation is associated
with a specific total level. You can also use level indicators on the ON-ERROR and ENDMON operations, but they
have no effect and would only be used for documentation purposes.
Conditioning indicators can also be used with the MONITOR operation. If the conditioning indicator is *OFF,
control is passed to the first statement after the ENDMON. This technique might be useful for debugging. You
cannot use conditioning indicators with ON-ERROR or ENDMON.
You cannot branch out of a monitor block; however, branching is allowed in the ON-ERROR block.
Let the Monitoring Begin
So what do you think? It's about time, right? I think so, too. I can already see it: support calls down, fewer 3:00 a.m.
pages, productivity at an all-time high. The sky's the limit, but you first have to implement it. Download my code
and add a call to a bogus program or generate a divide-by-zero error and see how monitoring works. Once you're
comfortable with the technique, you'll be on your way toward bulletproofing your RPG applications.
Kevin Vandever is a lead IT engineer at Boise Cascade Office Products in Itasca, Illinois, and is co-editor of
Midrange Programmer, OS/400
Edition. He can be reached at kvandever@itjungle.com.
|