A Style Guide For Modern RPG and ILE, Part 2
A Style Guide For Modern RPG and ILE, Part 2
A Style Guide for Modern RPG and ILE, Part 2 Volume 16, Number 23 -- October 18, 2016
T.L. Ashford
One of the basic principles of programming is that coding conventions (guidelines and
WorksRight Software
standards) improve the readability of source code and make software maintenance easier. Focal Point Solutions Group
Coding conventions provide the foundation for developing applications that are easy to
maintain and modify. This article completes the style guide to coding RPG programs using
free-form RPG in an ILE environment started in A Style Guide For Modern RPG And ILE, Part 1.
Older Functions
When using free-form RPG (and nothing but free-form RPG), a lot of the old RPG
“functionality” is no longer available (e.g., operation codes such as MOVE, MOVEL, GOTO, ADD,
SUB, condi-tioning and resulting indicators). But there are still some old features that are
available but should be avoided.
The use of RPG’s numeric indicators (*IN01 to *IN99) and special indicator (*INU1 to *INU8)
should be avoided at all costs. They are not self-explanatory and are dependent on comments
to clarify their usage.
Define your own indicators when a Boolean condition is required. Better to have something
like this:
if monthEnd;
As opposed to this:
if *in70;
If the program has externally described display files or print files, which make use of numeric
conditioning and/or resulting indicators, then define an indicator data structure to remap the
numeric indicators in the display or print file to indicators with meaningful names in the
program.
Table of Contents
dcl-F Mod30101D workstn(*ext) usage(*input:*output) IndDs(WSI); A Style Guide for Modern RPG and ILE, Part 2
dcl-Ds WSI qualified; Replacing Source in The Twenty-First Century
F3Exit ind pos(3); SQL PL Conditional Structures
F5Refresh ind pos(5);
F12Cancel ind pos(12);
F23Delete ind pos(23);
pageDown ind pos(26);
pageUp ind pos(27);
The definition of a compile time array means that the definition of the array (in the data
declarations), is separate from the definition of the data (which is at the end of the program).
It is better to define an array and its corresponding data in a data structure–the definition of the
data structure overlaying the definition of the data.
dcl-Ds allDays;
*N char(9) inz('January');
*N char(9) inz('February');
*N char(9) inz('March');
*N char(9) inz('April');
*N char(9) inz('May');
*N char(9) inz('June');
*N char(9) inz('July');
*N char(9) inz('August');
*N char(9) inz('September');
*N char(9) inz('October');
*N char(9) inz('November');
*N char(9) inz('December');
Content archive
monthNames char(9) dim(12) pos(1);
The Four Hundred
end-Ds;
Four Hundred Stuff
Four Hundred Guru
The definition of an array in the same location as the data makes it easier to modularize code
into subprocedures when required.
Data structure arrays should be used in place of multiple occurrence data structures. Multiple
occurrence data structures only allow access to one occurrence (element) at a time (e.g., cannot
directly compare two occurrences of a multiple occurrence data structure) and setting the
required occurrence (OCCUR/%OCCURS() ) is cumbersome, as opposed to simply using an
index to identify an array element.
Embedded SQL
Apart from the standard style guidelines, there are a few guidelines that relate specifically to
embedded SQL.
Keep SQL statements as simple as possible. You do not want to have to debug a complex SQL
statement in a RPG program. Create views that “hide” the complexity of joins and casting
and se-lect from the view in the RPG program.
Avoid naming variables that start with SQ, RDI, or DSN. They might conflict with variable
names that are automatically included by the SQL pre-compiler.
Use SET OPTIONS to ensure that the SQL environment is specified correctly at compile
time.
Wherever possible, make use of multi-row fetch as opposed to single-row fetch.
Global Definitions
The most valid candidates for global definitions are file and/or data area definitions. If they are
being used, they should be modularized within a module with the subprocedures that will be
processing them. Consider using qualified data structures for all I/O.
If global variables are being defined, they should be qualified (either in a data structure or with
a prefix such as g_) so they are easily identified as global variables within a subprocedure.
The use of the IMPORT and EXPORT keywords for variables should only be used as a last
resort and should be well documented when used.
Remember, prototypes are only required for external subprocedure or program calls. They are
not required for subprocedures that are coded and only called within the same
module/program.
Parameters
Return Value
Subprocedures can be used to code procedures, which do not return a value, and functions,
which do return a value. (A call to a program would be considered a procedure call, since
programs cannot return a value.) One of the items that is often debated is whether or not
subprocedures should always return a value.
The argument for always returning a value is that there is always a value to return. For
example, even if a procedure does not calculate and return a specific value, should it return a
value to indicate whether or not the process worked?
Copy Members
All prototypes should be coded in copy members and included in required programs and
modules. A prototype should never be coded in more than one source member.
The copy members that contain the definition of the prototypes should also contain the
definition of any templates and named constants that refer specifically to the use of the
corresponding prototypes.
Managing the maintenance and usage of the prototype members can be quite a challenge and,
at the moment, there is no single simple solution. Every solution comes with a cost.
The prototype members must be easy to maintain. You do not want a situation where two
programmers need to change prototype definitions in the same member at the same time. This
means that there will usually be a one-to-one correspondence between prototype members
and modules, i.e., a prototype member containing all prototypes, templates, and named
constants for subprocedures in a corresponding module. The same one-to-one
correspondence would apply to prototypes for program calls although a “program” prototype
member might contain prototypes for a group of related programs.
The ease of maintenance requirement means there will be quite a few prototype members.
There is now the challenge of how the programmer knows which members to include in a
program when a call is to be made to a program or an external subprocedure. This is even more
challenging if the prototype members are in a source physical file with a 10-character naming
restriction, there is little chance of having meaningful names. An alternative is to define
prototype members in directories in the Integrated File System (IFS), where meaningful names
can be given to the prototype members.
Compare the following directive to include a prototype member from a source file:
/include qrpglesrc,UTILITY01P
/include '/myApp/proto_utility/userSpaceAPIs.rpgle'
The requirement for /INCLUDE directives can be reduced by using nested copies. It is possible
to have a single /INCLUDE directive that would include all prototype members in the
application. Whereas this approach is appealing (the programmer only needs to know the name
of one copy member), there are a number of considerations:
Someone must be responsible for maintaining the extra copy members that handle the
nesting and grouping of the prototype members.
All prototypes, templates, and named constants (defined in prototype members) will now be
included in the Outline View in RDi. If there are a lot of prototypes, templates and named
constants–the Outline View will take a long time to refresh. This can be somewhat alleviated
through the use of conditional compilation directives (as you will see in a following
example) but that, in turn, leads to even more complicated maintenance of the extra copy
members that handle the nesting and grouping of the prototype members.
Change management systems may have difficulty when a change is made to prototype
member (even as simple a change as adding a comment). The change management system
may deter-mine that, since the member is included in every program and the member has
changed then every program should be recreated.
The following example shows one approach to using nested copies to minimize the
requirement for multiple /INCLUDE directives. A program contains the following /INCLUDE
directive:
/include common,baseInfo
/include utility,pUtility
/include fileProcs,protoFile
/include common,commproto
/include genstat,pStatGen
/include regFunc,pRegFunc
//--------------------------------------------------------
// Include CGI Prototypes, if required
/If Defined(CGIPGM)
/include CGIDEV2/QRPGLESRC,PrototypeB
/include CGIDEV2/QRPGLESRC,Usec
/EndIf
//--------------------------------------------------------
// Include HSSF Prototypes, if required
/If Defined(HSSF)
/include SIDSrc,HSSF_H
/EndIf
Each of the included members might contain prototype definitions or might contain further
nested include directives. For example, the PUTILITY member, in turn, contains nested include
directives, as follows:
/include utility,PUTILMSGS
/include utility,PUTILCGI
/include utility,PUTILSPACE
/include utility,PUTILIFS
/include utility,PUTILDATE
The members PUTILMSGS, PUTILCGI, PUTILSPACE, PUTILIFS, and PUTILDATE contain the
actual prototypes.
We can alleviate the overhead on the refresh of RDi’s Outline view by using compiler directives
to indicate what should and should not be included. Changing the definition of the PUTILITY
member as follows means that a definition name must be set in order for the prototypes to be
included.
/if defined(UTILITY)
/define UTIL_MESSAGE
/define UTIL_CGI
/define UTIL_USERSPACE
/define UTIL_IFS
/define UTIL_DATES
/endIf
/if defined(UTIL_MESSAGE)
/include utility,PUTILMSGS
/endIf
/if defined(UTIL_CGI)
/include utility,PUTILCGI
/endIf
/if defined(UTIL_USERSPACE)
/include utility,PUTILSPACE
/endIf
/if defined(UTIL_IFS)
/include utility,PUTILIFS
/endIf
/if defined(UTIL_DATES)
/include utility,PUTILDATE
/endIf
The originating program (that includes the member BASEINFO) can set the required definition
names or can define UTILITY, which would result in all members being included.
/define UTIL_MESSAGE
/define UTIL_USERSPACE
/include common,baseInfo
The “difficulty” with this approach is that the programmer must know the required definition
names–so documentation is required.
The benefit of this approach is that it documents what is being included in the program.
Note that in this example, definition names are being used to include single members, but they
could just as easily be used to include multiple members (as with CGIPGM in the BASEINFO
member).
As stated earlier, there is no simple solution to managing the maintenance and usage of the
prototype members. The challenge is to find which combination of techniques will provide the
best balance between ease of maintenance and ease of use.
There are many approaches to setting standards for the Integrated Language Environment
(ILE). Common questions are:
Unfortunately, the answer to all of these questions is: It depends. The structure of an ILE
application depends on the application and what it does.
One example of how to structure an ILE application can be found in my article Development
Environments.
A change management system is something that can have a major impact on the methodology
you use when developing an ILE application in that the change management system may have a
preferred technique for managing service programs, binder language, etc.
Regardless of the final development environment, these are some of the dos and don’ts for an
ILE environment.
ILE Programs
Although the structure of ILE programs can be more complicated (one or more modules,
binding to service programs), you still want the process of creating a program to be a simple
one. In order to compile a program, the programmer should not need to know about activation
groups and what all the service programs are, let alone what subprocedures are in what service
programs.
This can be achieved through the diligent use of binding directories and a standard control
specification, which gets included in every program via an /INCLUDE directive.
Service Programs
Service programs are at the core of any ILE application. If a subprocedure can be used in more
than one place, then it belongs in a service program.
Since content of a service program can be called from multiple places, the management of a
service program requires a bit more care than “normal” programs. The approach to managing
changes to a service program should be along the same lines as the way changes to a database
are managed. As with a database, the minimum of people should have the ability to make
changes. Any programmer can develop a subprocedure, but not any programmer can
incorporate it in a service program.
Binding Directories
When creating an ILE program or service program, binding directories provide a means of
generically providing a list of modules and service programs that may be required for binding.
Binding directories should be kept to a minimum. A single binding directory should list all
service programs that might be required when a standard program is created.
Depending on the complexity and cross referencing between service programs, each service
program might also require a binding directory. If there are multiple ILE applications and there
are service programs and modules that are common to all, then another binding directory may
be used to list those objects.
Remember, a list of binding directories can be included in the BNDDIR keyword on an RPG
control spec.
There have been cases where an application has one binding directory per program–the
binding directory lists the modules and service programs required to define the program. This
is a mistake and imposes a maintenance overhead that is not required.
Managing service programs with many modules has to be done either by having a specific
binding directory for the service program, by writing a CL program to create the service
program, or by using a generic name for all modules in the service program.
Activation Groups
ILE programs should never be run in the default activation group. This can only be achieved if
an ILE program is created with an activation group of *CALLER and the program is called by an
OPM program (or from a command line). It is best to use the name of the activation group when
creating programs. This is easily achieved using the ACTGRP keyword in a standard Control
Spec in the programs.
Even worse than having an ILE program in the default activation group is having a service
program in the default activation group. This can only happen if an ILE program is running in
the default activation group. Again, ILE programs should never be run in the default activation
group.
Also, avoid using the QILE activation group. This is the activation group that is used when care
has not been taken to choose the activation group. If care was not taken to choose the activation
group, care may not have been taken in other aspects of the application, and your application
may be subject to the activation group ending unexpectedly, or to overrides and commitment
control actions that you do not want.
Hopefully, these guidelines will provide a starting point for setting your own guidelines and
standards. Another excellent source for style guidelines may be found in Appendix D of “Pro-
gramming in RPG, 5th Edition” by Jim Buck and Bryan Meyers. Please let me know if you have
any comments or additions.
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.
RELATED STORIES
Development Environments
Tags:
Sponsored by
Briteskies
From RPG development, technical consulting services, and EDI solutions, to eCommerce
integration and security audits, Briteskies can handle it all. As a full-service consulting
company, we have IBM i experts in place to help you on a short-term project basis or long-
term assignment.
Read our blog for more information about our expertise. Check out our Customer Success
Stories to see how we help companies improve their efficiency.
Learn more at www.briteskies.com or Contact Us Now to see how we can help keep your
business running.
Sponsored Links
Chrono-Logic: Deploy automatically to multiple IBM i and Windows servers with a single
click!!
Fresche: IBM i staffing for all of your IT needs. Request a FREE estimate. 1-800-361-6782
Manta Technologies Inc.: The Leader in IBM i Education! Download catalog and take sample
sessions!
Mobile Apps As Easy As RPG III On Your IBM i Radar Now: GDPR
Leave a Reply