Midrange News for the IBM i Community

Posted by: Bob Cozzi
Rogue Programmer
Cozzi Productions, Inc.
A Bakers Dozen or so Useful RPG IV Tips from Bob Cozzi
has no ratings.
Published: 06 Nov 2014
Revised: 19 Feb 2015 - 3298 days ago
Last viewed on: 01 Mar 2024 (9165 views) 

Using IBM i? Need to create Excel, CSV, HTML, JSON, PDF, SPOOL reports? Learn more about the fastest and least expensive tool for the job: SQL iQuery.

A Bakers Dozen or so Useful RPG IV Tips from Bob Cozzi Published by: Bob Cozzi on 06 Nov 2014 view comments(3)

RPG IV Suggestions

Below is a list of a dozen or so useful RPG IV tips. These are from my experience as a programmer with the language. Feel free to post comments and questions below. I will reply in kind. Also your simple and direct tips are welcome.

  1. When using the *INZSR, move any processing related to Entry parameters into *INZSR.
  2. Use parens around conditional (IF) statements.
  3. Use free-format Embedded SQL exclusively (verses fixed format).
  4. Use Subprocedures exclusively; avoid using Subroutines.
  5. Use the INDDS in place of *INxx indicators when using Display or Print files.
  6. Use Qualified Data Structures exclusively, except in rare/exception situations.
  7. Use Integer data types for counters, array indexes and other whole numbers (10i0).
  8. Use Packed(7,x) or Packed(15,x) for Packed "work fields" to improve performance.
  9. Use VARYING character variables.
  10. Use %trimR and avoid blindly using %trim by default.
  11. Use MONITOR and ON-ERROR to trap most errors and the (E) operation extender for single-opcode error trapping.
  12. Use "EntryPList" as the "procedure interface" name for *PGM parameter lists.
  13. Use EXTBININT(*YES) on the Header specification to convert legacy DB2 "B" fields to Integer.


Advantages of each of the above techniques

  1. The *INZSR is called before the app jumps into the first "Detail Time" Calcs. So the code at the top of your program, will run after the *INZSR subroutine. By placing the parameter processing routine in *INZSR, you avoid any timing issues with regards to accessing the parameter data within *INZSR or any other routine *INZSR may call. It's also nice to know that the parameters are properly consumed before the main program begins.
  2. Using Parens around conditional statements helps with the priority of the evaluation. This is most helpful to the "next" programmer who reads and then modifies your code. The clarity often provided with parens is obvious, and should be a habit rather than an exception.
  3. The ease with which to code embedded SQL in free-format is so much better than fixed format, it isn't even funny. Using the EXEC SQL prefix and just start writing the SQL statement. No goofy /EXEC-SQL and /END-EXEC compiler directives to deal with, just EXEC SQL SELECT… followed by a semi-colon.
  4. Let's face it, if RPG had subprocedures originally, it would never have had subroutines. Being able to declare local variables (aka work fields), parameters and receive returned values back is what real programmers do. If you're still using subroutines and do so not because of legacy issue, but because you think they're better, well, you're wrong.
  5. Using *INxx indicators is dumb but often can't be avoided. To help with that, RPG has the Indicator Data Structure that works with Device Files (WORKSTN and PRINTER). This data structure is used to map the indicators used in those device file's DDS, to data structure subfield names. This allows you to manipulate the indicators expected by the device file, but not actual reference *INxx variables in your code. One note, however. When using INDDS, the corresponding *INxx indicator in the RPG IV program cannot be used as an alias for the INDDS subfield. Only the subfields provide access to that device's indicators.
  6. What can I say "Where have you been all my life?" Qualified Data Structures are the only way to go 99.9% of the time. They provide a way to keep "work fields" within a context. For example, all the Invoice fields are stored in the data structure named INV and are referenced as INV.CUSTNO INV.ORDDATE, INV.SHIPDATE etc. We no longer have to worry about which name to use for a field while doing program maintenance. Consider switching your programming style to Qualified data structures exclusively.
  7. Integer variables are the only way to optimize DO/FOR loops and array indexes. Packed Fields are no longer "the fastest numeric data types" that crown goes to Integers, no exceptions.
  8. When using packed numeric fields, the system has been optimized for 7 and 15 digit packed variables. All other variables are up-converted to either 7 or 15-digit packed. There may also be a similar scenario for larger than 15 digit packed values, but I don't have the data on that. So if performance of certain areas of math is your issue, consider just using a 7- or 15-digit packed value (decimal positions don't matter) as your work-variable size. If you think you're saving space using that 5P0 work field, you're really not. The system will declare an additional "temp" variable that is Packed(7,x) under the covers. No space saved here, plus there's the time it takes to move your data into Packed(7,x) and then back again.
  9. Variable length fields allow RPG to only process the "current" length of the data in the field. This can lead to better performance. However, what I love about VARYING fields is that when you're building a text string you can simply concatenate them together and often times avoid %TRIMR functions. Likewise, to append something to a VARYING field, as simple v += a; syntax is all that's needed. Compare that the V = %trimR(V) + a; that is needed for fixed length fields.
  10. If you think about it, the Trim-Right and Trim-Left built-in functions do one thing; they remove trailing or leading blanks and other characters from a character variable (fixed or varying). The %TRIM built-in function performs both of those tasks. In most cases, %TRIMR is all that's really needed, yet, time after time, I see %trim(xyz) being used when %trimr(xyz) is all that's necessary. When I see this, I say "Oh, the programmer is actually trying to make the code slower. Why not read a database record twice just for fun? (read sarcasm in that last line). Create a habit of using %TRIMR as your default/goto built-in function for trimming, and only use %TRIM when you really mean it.
  11. The relative new MONITOR/ON-ERROR opcodes in RPGIV allow you to trap any error that occurs within a group of opcodes. You can also filter what code handles the error, based on the error code or family of error codes. While it isn't perfect, I find it extremely helpful for decimal data errors, overflow issues, range/substring outside of limits, lock conditions, etc. When you want to trap just the result of one opcode, it can be simpler to code the (E) operation extender instead of using MONITOR/ON-ERROR; that's what I do. The only real shortcoming is that when you only want to trap and swallow the error, you have to code MONITOR/ON-ERROR/ENDMON which is basically an empty ON-ERROR routine. Looks a bit confusing in code, but what are you going to do?
  12. Since many programmers have moved their *ENTRY PLIST "opcode" parameters to the D specs, there have been some issues with name-collision if you name the prototype and procedure interface the same as your source member/program. For me, I find it annoying to code the program name as the interface name (PI block) and then if I change that program's name for any reason, I also have to modify the PI statement. What I found is that naming the program's PI statements with a throwback name of "EntryPList" works fine. The program itself is always identified on the EXTPGM keyword of the Prototype (also required for D-spec based entry parameters). So why not reduce the redundancy by using some other name. Certain "WhatEver" is also good enough, but I like the nostalgic name "EntryPList" it really helps the next programmer know what's going on in the code.
  13. Files created using DDS do not directly support Integers, while SQL DDL does. However, RPG IV will define these fields as B (binary) data types when you compile your source member. By adding the EXTBININT(*YES) or the simpler EXTBININT keyword to your header specification, all B data type fields are declared as the contemporary I (integer) data type; and everything is wonderful.

Return to midrangenews.com home page.
Sort Ascend | Descend


(Sign in to Post a Comment)
Posted by: starbuck5250
Premium member *
Comment on: A Bakers Dozen or so Useful RPG IV Tips from Bob Cozzi
Posted: 9 years 3 months 25 days 23 minutes ago

Great list Bob!  Here are two more I think are indispensable:

13. Always test SQLSTATE after an executable SQL statement and do something with the result.

14. Always use %error() or use the error indicator on RPG READ/CHAIN/UPDATE/WRITE record level I/O and do something with the result.

Constraint violations show up as I/O errors, so having decent error handling is a prerequisite to using RI and triggers which validate data.

Posted by: mrqueue
Premium member *
Comment on: A Bakers Dozen or so Useful RPG IV Tips from Bob Cozzi
Posted: 9 years 2 months 9 days 1 hours 22 minutes ago

To quote a friend of mine: That's bloody brilliant! Thank you for those great tips Bob. 

Posted by: Ringer
Premium member *
Comment on: A Bakers Dozen or so Useful RPG IV Tips from Bob Cozzi
Posted: 9 years 1 months 17 days 22 hours 16 minutes ago
Edited: Mon, 12 Jan, 2015 at 15:47:33 (3336 days ago)

I don't get #1. *INZSR only runs the first time RPG *PGM is called. If you exit with *INLR=*OFF, it won't run again on the next call (right?). The only code I typically put in *INZSR is to get the system name. I always code a STARTUP routine (and SHUTDOWN) and examine entry parameters there. I guess I could call *INZSR directly but why get the system name repeatedly, it's a constant. Thanks. 

Chris Ringer