Guild Companies, Inc.  
RJS Software Systems
 
Midrange Programmer - How To, Advice & Free Code
OS/400 Edition
Volume 1, Number 1- January 17, 2002

RPG Prototyping

by Ted Holt

Would you rather find an error in a program at compile time or at runtime? If you said compile time, read on. If you said runtime, either find yourself another profession or invest in a good set of ear plugs to block out all the complaints you'll be getting from your users!

I hate to get a call from a user because of a runtime error. It ruins my day because I've got to drop whatever I'm working on, figure out what happened, and then figure out how to recover from the error. I've got better things to do. I'd prefer to find the error at compile time.
RJS


One of the ways I convert some runtime errors into compile-time errors is through prototyping. Prototyping allows the compiler to verify that the parameters that will be passed to a called program or procedure are acceptable--that is, that they are defined appropriately.

Prototyping has several advantages, but in this article I only discuss how prototyping can convert runtime errors into compile-time errors.

Giving the Compiler More Information

A prototype tells the compiler how the parameters of a called program or procedure are defined. The program may be an OPM program or an ILE program. It doesn't matter which language it was written in; you can prototype any *PGM object.

For example, suppose I have a CL program that defines two parameters--an option and a five-digit packed-decimal return code--like this:

PGM    PARM(&OPTION &RETURNCODE)    

DCL    VAR(&OPTION) TYPE(*CHAR) LEN(1)
DCL    VAR(&RETURNCODE) TYPE(*DEC) LEN(5)

IF  (&OPTION *EQ A) +
      (CHGVAR VAR(&RETURNCODE) VALUE(-25))
ELSE IF  (&OPTION *EQ B) +
      (CHGVAR VAR(&RETURNCODE) VALUE(5))
ELSE + 
      (CHGVAR VAR(&RETURNCODE) VALUE(25))

RETURN 
ENDPGM 


To invoke this CL program from an RPG IV program, I use the CALL op code. What happens if I don't properly define the return code? What if, for example, I define it as a three-digit number rather than a five-digit number?

D OPTION       S            1     
D RTNCODE      S            3  0  C     EVAL   OPTION = 'C'
C           CALL  'YPGM01CL'
C           PARM                OPTION
C           PARM                RTNCODE
C           IF    RTNCODE < *ZERO


You guessed it: My RPG IV program blows up with a decimal data error (RNQ0907) when I use the return code field in the IF operation.

I can avoid this problem by creating a prototype. In the prototype, I indicate how the parameters are defined.

D YPGM01CL      pr           extpgm('YPGM01CL') 
D   Option              1a
D   ReturnCode          5p 0

I store this prototype in its own source member. In this case, assume I store the prototype in member YPGM01CL, in source physical file PROTOTYPES, somewhere in my library list. You don't have to store the prototypes in a copy book. But by doing so, you have an easy way to reuse the prototype in other programs without retyping it each time.

Compare the prototype to the CL code above, and you will see that the two parameters are correctly defined--a one-byte character value and a five-digit packed-decimal value.

Notice the EXTPGM (external program) keyword. This tells the name of the program that will be called.

Here is the same RPG caller, changed to use the prototype.

 /copy prototypes,ypgm01cl                               
D OPTION     S              1
D RTNCODE    S              3P 0
C              EVAL    OPTION = 'C'
C              CALLP   YPGM01CL (OPTION : RTNCODE)
C              IF      RTNCODE < *ZERO

The /copy directive copies the prototype into the member so that it can be compiled as part of the program.

Notice that the CALL op code has been replaced by CALLP. The parameters are listed in the parameter list of CALLP. Both option and return code fields are defined as they were in the previous example.

When I try to compile this program, I am stopped by error RNF7535 (The type and attributes of the parameter do not match those of the prototype). The compiler generates this error because the second parameter is defined as a 3-digit packed-decimal field, but the prototype I copied into the program, via the copy book, specifies that the called program is expecting a 5 position packed-decimal field. So, instead of a runtime error, I now have a compile-time error. And if you're like me, you'd rather catch the errors yourself at compile time rather than have the user catch them at runtime.

Now, I hear some of you objecting. You're saying, "I would catch an error like this when testing." I agree fully. With short, simple, little programs like these, you would. But how many programs do you have that are as simple as this example? What about a caller of several hundred or maybe even thousands of lines of code, with plenty of calls, each one with a list of parameters? What if you decide to enlarge a field that is used as one of those parameters? Is there a chance you might overlook that the field whose size you're changing is used as a parameter for a CALL, and that the called program is not going to be changed?

How can you be sure that your testing will catch the problem? In my little example, the return code field is used immediately upon return to the caller. What if the return code is buried in conditional logic that gets executed only once in a blue moon? What's likely to happen is that, days or weeks or months later, you'll get a call from a user with a runtime error. Good luck trying to find the bug then!

Before you get the impression that this method is foolproof, let me give you a warning. You, the programmer, have to be sure that you code the prototype correctly. If I had defined the second parameter in the prototype as a three-digit field, the modified RPG program would have compiled, because the second parameters' attributes would have matched; however, I still would have gotten a runtime error, because neither of the defined second parameters matched the CL program's second parameter. This is why I placed the prototype in a source member and /copy'd it into the calling program. Every RPG IV caller uses /copy and gets the same prototype. This reduces the chance that one or more callers will have an error in the prototype.

Calling a Module

If you've built modules with commands like CRTRPGMOD, CRTCLMOD, CRTCBLMOD, and CRTCMOD, you're accustomed to calling them with the CALLB op code. Just as you can with CALL, you can replace CALLB with CALLP.

Here is the source code for RPG module YMOD01RG, which is entrusted with the important task of doubling a number and adding 1.23 to it.

D number          S             11p 2
C     *entry        plist
C                   parm                    number           C*
C                   Eval      Number = (Number * 2) + 1.23

C*
C                   return

To call this module the old way, you use CALLB.

D SomeNumber      s             11  2

C                   eval      SomeNumber = 265.21
C                   callb     'YMOD02RG'
C                   parm                    SomeNumber

But you can prototype it instead.

D WeirdCalc       pr                  extproc('YMOD02RG') 
D    ANumber                    11  2

Notice I've used the EXTPROC (external procedure) keyword instead of EXTPGM. I used EXTPROC because YMOD02RG is a module object, not a program object.

Notice also that I did not use the module name in column 7 of the first D-spec. I could have, but I don't have to. A prototype does not have to have the same name as the external program or procedure to which it refers. So, if you have an RPG program called CA1255 for file maintenance over the customer master file, you can call it ChangeCustomers or CustomerMaint, or whatever you like.

Now, instead of CALLB, use CALLP to invoke the module.

 /copy prototypes,ymod02rg
D SomeNumber      s             11  2

C                   eval      SomeNumber = 265.21
C                   callp     WeirdCalc (SomeNumber)

Prototyping Is Good

If you're an RPG programmer, you probably have rules that you code by, such as never use a conditioning indicator in calculation specs, or never use the RPG cycle. Consider adding another rule: Never use a CALL or CALLB operation in an RPG IV program. Prototype your calls from now on, as a way to prevent runtime errors.

In my next article, I will continue my discussion on prototyping. I'll teach you how to write internal subprocedures and offer my opinion about why it's better to use internal subprocedures rather than executing a subroutine.

Ted Holt is a consultant who lives in northeast Mississippi. He edits the OS/400 edition of the Midrange Guru newsletter. You can contact Ted at tholt@itjungle.com.

Sponsored By
RJS SOFTWARE SYSTEMS

Save TIME AND MONEY with our
AS/400 - iSeries Report & Data Delivery Systems

RJS Software Systems, Inc. - http://www.rjssoft.com

Delivering AS/400 reports via email, web, Lotus Notes, Domino or CD.

Whether it's Native AS/400 or PC-based, we have the solution.

* WinSpool/400 report download and conversion
* Text converters (ASCII, RTF, PDF, HTML, Spreadsheet)
* Form converters (AFP, EZ/Print, Formation, Formsprint, & CreatePrint to PDF)
* Split reports with AS/400 Report Splitter
* Email Report Server, Web Report Server
* Domino Report Server
* Batch Report Server delivery to LAN, IFS and FTP
* SMTP/400, POP/400 & FTP/400 Email/Transfer APIs
* Query and Convert AS/400 Files with DataExport/400
* Web-Based AS/400 Data Access via Active Server Pages

For a FREE FULLY FUNCTIONAL DEMO CD, please visit our Web site at http://www.rjssoft.com.

Contact us at 888-RJS-SOFT or email us at sales@rjssoft.com

THIS ISSUE
SPONSORED BY:
Help/Systems
SoftLanding Systems
BCD Software Int'l
Jacada Ltd.
RJS Software Systems
WorksRight Software, Inc.
BACK ISSUES
TABLE OF CONTENTS
Welcome to Midrange Programmer, OS/400 Edition
Determining a PC's IP Address
I Lost My License Key!
Calling a Program Using the iSeries Toolbox for Java
RPG Prototyping
JavaServer Pages 101
  Newsletters | Subscribe | Advertise | About Us | Contact | Search | Home  
  Last Updated: 1/17/02
Copyright © 1996-2008 Guild Companies, Inc. All Rights Reserved.