A Style Guide For Modern RPG And ILE, Part 1
October 4, 2016 Paul Tuohy
One of the basic principles of programming is that coding conventions (guidelines and standards) improve the readability of source code and make software maintenance easier. Coding conventions provide the foundation for developing applications that are easy to maintain and modify. This article and an article to follow are a style guide to coding RPG programs using free-form RPG in an ILE environment.
When developing guidelines and standards, one of the major challenges is to determine what is a standard and what is a guideline. For example, code indentation would be a standard, but whether the code is indented by two, three, or four characters would be a guideline. One of the objectives of standards is to ensure that required coding standards are implemented without impeding the creativity of the programmer.
Just Another Programming Language
RPG has now achieved the distinction of being just another programming language. This means that many of the style guidelines and standards that apply to other programming languages (Java, PHP, etc.) now apply to RPG as well. If you are developing in a multi-language environment, try to apply consistent standards to all languages. For example, if the guideline for PHP is to indent code by three characters, the guideline for indenting in RPG should be the same. Having consistent standards across all languages makes it easier for programmers to switch between languages.
Even if you are not developing in a multi-language environment, you can still learn from other languages. For example, use /INCLUDE instead of /COPY and use all uppercase for named constants–these are conventions that are common to most (if not all) programming languages.
Use the Right Tools
RDi (Rational Developer for i) can help with the implementation of standards. Learn to make use of Automatic Indent, the Outline View, Content Assist, Templates and Snippets: they can all help automate standards and styles.
RPG Is Free-Form
Modern RPG programs should contain only free-form code–they should not be a mixture of fixed-form and/or extended factor-2 and/or free-form. Complete free-form means that a lot of old “bad habits” (MOVE, MOVEL, GOTO, etc.) are no longer available. Coding only free-form code means that a programmer will not accidentally fall back onto old habits and coding practices.
If a decision is made that existing programs are not going to be converted to free-form RPG, then any modifications to the programs should be made in free-form only.
Whether writing new programs or modifying existing programs, only code in free-form RPG. However, when modifying existing programs, avoid creating the situation where the code switches frequently between fixed-form and free-form code. If you are making small modifications to fixed-form code, consider changing that section of fixed-form code to free-form before making your changes.
Modern RPG Programs and Subprocedures
A modern RPG program uses free-form syntax and is modular in structure. This modular structure is implemented with subprocedures. Subprocedures may be coded internally in a module or they may be external (in a service program or another bound module).
Although multiple subprocedures will be coded within a module, the approach to writing a subprocedure should be that the subprocedure is standalone. This means that the design of a subprocedure is such that it makes copious use of local variables as opposed to global variables, and that all required data that is external to the subprocedure is passed as parameters and/or a return value. Simply put, think of every subprocedure as a standalone program.
This approach to writing subprocedures means that when it is determined that a subprocedure may be useful in other places, it is a simple process to remove it from its current module and place it into a service program.
A subprocedure should be designed to perform one task (calculate_Pay(), get_customerData() etc.). It is OK if the subprocedure has to call other subprocedures to achieve that single task. This, in turn, means that subprocedures should be short and to the point. A good rule of thumb is to be able to see all the executable code for a subprocedure in a single window in RDi.
Also, when you find yourself defining global variables, stop and ask yourself: Why?
Before looking at the components of naming conventions, remember that although RPG is a mixed-case language, it is not a case-sensitive language. This means that, in an RPG program, the variable names customerID, CustomerID, customerid, and customerId all refer to the same variable, whereas in a PHP or Java program they would be four different variables. However, you should strive to use the same mixed-case form everywhere you use the name.
Names should be meaningful. The restriction of 10-character system names on IBM i has made RPG programmers masters of abbreviation. It is a habit that needs to be broken. Names should be meaningful and descriptive, and should not be restricted by a length (although perhaps you should avoid creating names up to the maximum allowed length of 4096). Just as a name should not be overly abbreviated, it should not be overly verbose. For example, the variable should be customerID, not cusID or theIDOfTheCustomer.
When naming variables, arrays and data structures, think of the name as a noun–it simply states what the “item” is. For example, currentAccountNumber, customerID, or customerList.
When naming subroutines, subprocedures or prototype names, think of the name as a verb combined with a noun. In other words, there is an action and an item. For example calculate_Pay(), get_customerData(), or convert_toCelsius();.
Use consistent abbreviations in your names. For example, if you have several procedures that do the “convert” action, be consistent in how you name the action (“convert”, “cvt”, “conv”, and so on).
Names (with the exception of named constants) should be mixed case. The usual standard is to use CamelCase. CamelCase means that a name is made up of compound words where each word begins with a capital letter. The first word may start with a capital letter or with a lower case letter but all following words would start with a capital letter. For example, CurrentAccountNumber or currentAccountNumber.
But, since RPG is not case sensitive, it is up to the programmer to follow the guideline.
With the exception of the underscore character, special characters (i.e., @, #, $) should not be used in names. Some of the special characters are prone to change dependent on CCSID definitions and should be avoided.
The underscore character can be used to add clarity to a name. There is an inclination to use underscore to separate compound words in a name, but this is usually superfluous when CamelCase is being used. The name currentAccountNumber is just as legible as current_Account_Number.
But underscore can be useful when used with subroutine, subprocedure or prototype names. The underscore is used to separate the action from the item: calculate_Pay() or get_customerData().
Given the following line of code:
salary = calculatePay(97);
A programmer would need to check whether salary was being set by a call to the subprocedure calculatePay() or from element 97 of the array calculatePay. The use of underscore in subprocedure names would add clarity to the code.
salary = calculate_Pay(97);
An underscore is also used to separate the words in a named constant (if your standard is to use all uppercase for named constants).
Use named constants instead of literals. Constant names make code self-documenting and easier to maintain. The exception to this is the use of 0 and 1 in expressions when clearing, incrementing, and decrementing field values. A constant name should reflect the function of that constant, not the value.
The convention in most programming languages is that constant names are all uppercase. Accordingly, underscore should be used to separate compound words within the name.
Compare the use of a literal:
if (%status(myFile) = 1218);
With the use of a named constant:
if (%status(myFile) = ERR_RECORD_LOCKED);
If literals are standard throughout an application (i.e., status codes), the named constants should be defined in a copy member and included in programs as required.
With the proviso that names should be meaningful, naming conventions can be used to identify a usage or grouping of variables or named constants. The convention is that the correlated names start with the same characters followed by an underscore.
For example, a copy member includes the following named constants used to identify message IDs. All of the named constants begin with MSGID_.
dcl-C MSGID_CODE 'ERR0001'; dcl-C MSGID_DESCRIPTION 'ERR0002';
Use a standard prefix or suffix to distinguish template variables and files. For example, use a prefix such as type_, or typ_, or t_, or use a suffix such as _T, or _typ, or _type.
In the rare occasion when global variables are used in subprocedures, the names of the global variables begin with the characters g_.
When naming subprocedures and prototyped program calls, it is imperative that the naming convention is consistent. For example, subprocedures that add information to a database should start with add_ or write_, not a mixture of the two.
Naming conventions are definitely required for names that are defined through copy members or globally defined in a program/module. But the use of local variables (in subprocedures) and qualified data structures minimizes the requirement for naming conventions within subprocedures.
All programs need to be documented. One of the major benefits of free-form RPG and proper naming conventions is that it reduces the need for detailed documentation because the code is self-explanatory. Even so, programs need to be documented.
Comments should be used in two ways in RPG programs and subprocedures:
Summary comments should be at the start of every program and subprocedure. Summary comments should contain, at least, the following information:
One of the major benefits of free-form RPG and proper naming conventions is that it reduces the requirement for detailed commenting. Detailed commenting should only be required to explain some complex coding technique or to highlight a technique being used in the code.
Use blank lines to group and segment code.
It can be useful to use a marker line comment to separate major sections of a program, although the requirement for this is somewhat nullified when using the Filter View feature in RDi.
Positions 1 to 5
Historically, positions 1 to 5 may have been used to indicate or flag lines that were changed for a certain modification. This practice should be avoided. Specifying **FREE on the first line of code means that positions 1 to 7 on all subsequent lines may be used for code.
All code should be structured. The standards and guidelines for declarative code and executable code will be slightly different.
Declarative code is defined at the start of a module, program, or subprocedure. Definitions should be grouped together by type of declaration.
Declarations should be grouped so that related items are defined together. The procedure interface should be first, before any other declarations.
Indentation should be used with data structured to identify overlaying structures.
Align definitions so they are easy to read. For example, when defining stand-alone variables, parameters or data structure subfields, align the data type on each line. Compare the definition of this data structure with alignment.
dcl-Ds APIError qualified; bytesprovided int(10) inz(%size(APIError)); bytesavail int(10) inz(0); msgid char(7); *N char(1); msgdata char(240); end-Ds;
To one without alignment:
dcl-Ds APIError qualified; bytesprovided int(10) inz(%size(APIError)); bytesavail int(10) inz(0); msgid char(7); *N char(1); msgdata char(240); end-Ds;
All executable code should be indented. Indentation within loops and groups adds to the legibility of the code.
if (messageCount() = 0) ; select; when CGIOption = 'CANCEL'; when CGIOption = 'DELETE'; failed = delete_Event(persistId); other; set_eventData(persistId : data); if (messageCount() = 0); failed = put_Event(persistId); endif; endSl; endIf;
If a statement takes more than one line of code, use alignment to make the code more legible.
set_days_for_Event(data.event: %date(data.fromdate: *USA): %date(data.todate: *USA): %date(data.wrkshpdate: *USA));
Automatic indent (and closure of control block) can be set in the ILE RPG preferences for the Remote Systems LPEX Editor in RDi.
Deeply nested IF/ELSE/ENDIF code blocks are hard to read and result in an unwieldy accumulation of ENDIFs at the end of the group. Instead use the more versatile SELECT/WHEN/OTHER/ENDSL or the IF/ELSEIF/ENDIF constructions.
Use SELECT/WHEN if the choice statements all compare a particular variable to a set of values. Use IF/ELSEIF if the choice statements have varied types of conditions.
Use indentation and alignment to make SQL statement legible. An SQL formatter is available in Run SQL Scripts in IBM i Access Clients Solutions.
exec sql declare C001 scroll cursor for select event, daynum, agendaid, fromtime, totime, showseq, title from AGENDA where event = :eventIn order by event, daynum, showseq, agendaid for read only;
Use Templates and Qualified Data Structures
Templates and qualified data structures provide a means of clearly defining and documenting data items in a program.
A qualified data structure means that all references to the data structure subfields must be qualified with the data structure name, fors instance, mailAddress.city, mailAddress.state. This allows for subfields with the same name to be defined in multiple data structures without the possibility of conflict and so that the association between a subfield and its data structure is clear.
Defining a qualified data structure as a template means that the data structure may not be used as a data structure but can be used as the template for other data structures that are defined using the LIKEDS keyword. In the following example, a reference to the subfield baseAddress.city would be invalid but a reference to the subfield mailAddress.city would be valid.
dcl-Ds baseAddress template qualified; street1 char(30); street2 char(30); city varchar(20); state char(2) inz('MN'); zip char(5); zipplus char(4); end-Ds; dcl-Ds mailAddress likeds(baseAddress) inz(*likeDS);
Templates and qualified data structures provide an excellent means of gathering together related “work” variables in a program or providing parameters for a call interface. The definition of the template data structure can be placed in the same copy member as the prototype definition for a called program or subprocedure.
The LIKEREC keyword can also be used to define qualified data structures based on input/output records for an externally described file.
You can also define template fields (with the LIKE keyword) and files (with the LIKEFILE keyword).
Qualify Wherever Possible
When using built in functions such as %EOF(),%FOUND(),%EQUAL() and %STATUS(), always provide the associated file name as a parameter.
Unfortunately, the %ERROR() built in function does not allow for a file name parameter, so always check the %ERROR() function (or the %STATUS() function) directly after an operation with an error (E) extender. Or save the setting to a variable so that you can test it later.
The more that you program for the web, the more you work with strings.
When it comes to string handling, it is better to use varying length (VARCHAR) as opposed to character (CHAR) fields. The use of varying length fields reduces the requirement for string functions (%TRIM etc.) and makes the code more legible.
Subroutines should not be used for modularization/structure. Subprocedures should be used instead. But subroutines can be useful in organizing the logic in a subprocedure.
To Be Continued
The rest of the guidelines will be in part 2!
Paul Tuohy is CEO of ComCon, an iSeries consulting company, and is one of the co-founders of System i Developer, which hosts the RPG & DB2 Summit conferences. He is an award-winning speaker who also speaks regularly at COMMON conferences, and is the author of “Re-engineering RPG Legacy Applications,” “The Programmers Guide to iSeries Navigator,” and the self-study course called “iSeries Navigator for Programmers.” Send your questions or comments for Paul to Ted Holt via the IT Jungle Contact page.
Thanks for the great article. One question if I may. “Even if you are not developing in a multi-language environment, you can still learn from other languages. For example, use /INCLUDE instead of /COPY…” I’ve been trying to find out the difference, and it appears to be how the SQL precompiler handles the instruction. Am I right to say /INCLUDE won’t accept embedded SQL in the source to be copied/included?
So that sounds like a serious limitation to /INCLUDE. How would you get around it?