Subprocedure Tips and Techniques
Author: Bradley V. Stone
Incorporating the use of subprocedures into your applications should be on everybody's "to do" list these days. You don't need to drop everything and simply rewrite your applications to take advantage of subprocedures and many of the other benefits that the Integrated Language Environment (ILE) has to offer. But, when you are writing new applications or performing major updates to existing components, you should look at utilizing these valuable, yet underused, technologies.
At a high level, subprocedures seem straightforward. If you have developed your applications to be somewhat "modular" by either calling external programs or using subprocedures to make the main line of your programs easier to read and follow, subprocedures make sense.
But before you get too deep into your first big project that incorporates subprocedures (either through the use of modules or service programs), it is a good idea to learn about a few important oversights and useful tricks from people using ILE every day.
One Function and One Function Only
If you are used to writing your applications using subroutines, you may have a habit (viewed negatively by some people) of putting too much processing inside a particular routine.
When writing ILE subprocedures, remember that to get the most return from your efforts, you should focus on creating subprocedures that do one thing and one thing only. If a subprocedure performs too many tasks, in the future, if you want to use just part of that task, you end up with redundant processing inside of your subprocedures.
For example, suppose you write a subprocedure named #updateSales. This subprocedure could have been written to update the sales information for a customer record when an invoice is created. Passing the customer number and sales amount into this subprocedure updates the sales totals for the customer as well as the associated AR records.
Later, you are updating an AR program that performs adjustments on the AR records for a customer. You realize your #updateSales subprocedure already performs half of the job you want. If you're in a rush, you probably would copy the half of the #updateSales subprocedure into an #updateAR subprocedure. This idea works fine until updates are required to one subprocedure or the other. Soon you realize this "time saving idea" made more work for you in the long run.
If when you ask yourself "what should this subprocedure do?" and you answer with a sentence containing a conjunction (such as "and" or "or") you have a problem. You have more work to do to split the functionality up into smaller subprocedures.
For example, what does the #updateSales subprocedure do? It updates the customer sales and the AR records. In this case, you should have first created a subprocedure to update the customer sales (such as #updateCustSales), and then another subprocedure to update the AR records (such as #updateAR). Each piece of this solution can be called independently, or together. Now, you can still have an #updateSales subprocedure if and only if it calls the two subprocedures mentioned. But it should do nothing else.
Passing Parameters by Reference or Value
Because the option of passing variables to programs/subprocedures wasn't directly available before ILE, now you must make a choice when you design applications. When you create ILE applications, you have the choice to pass parameters by reference or by value. The way the parameter is passed is specified on the procedure interface and prototype definition.
When a parameter is passed by reference, only the pointer (location in memory) to that variable is being passed. Whatever the called program or subprocedure does to the variable affects the value in the calling application. When a parameter is passed by value, the called program or subprocedure gets its own copy of the variable in memory. Changes to this variable will not be reflected in the calling program.
If the keyword value for a subprocedure parameter is left blank, the parameter is passed by reference. This approach is useful in situations where you want to pass a value into a subprocedure and have it manipulate the contents of the variable so the changes will be usable in the calling program.
The keyword value CONST may also be used to pass a variable by reference. A main difference between using the CONST keyword and leaving the keyword blank is that the subprocedure will not be allowed to manipulate the contents of the variable. Another important use for this keyword is that you are able to pass literal values as parameters into the called subprocedure.
The keyword VALUE is used to specify that this parameter is passed by value. In this case, the parameter can be manipulated by the called subprocedure, but because it is passed by value, any changes made to the value of the parameter will not affect the value of the variable in the calling program. Using the VALUE keyword is also useful if you want to be able to pass literal values as parameters. But, if you use subprocedures in CL programs, be aware that because CL programs cannot pass parameters by value, that this option will not work with CL programs.
When you are choosing which keyword values to use, you need to look at how the parameter will be used in the subprocedure. In most cases, if the parameter is not being used to return a value to the calling program, the CONST keyword will be a benefit in that the value will not "accidentally" be changed in the subprocedure. Also, using CONST value lets you pass literals and different sized variables from those defined in the prototype. So passing a 100-character string when the parameter is defined as a 200-character string should cause no problems.
Understanding the Scope
One of the biggest advantages to using subprocedures is that you are allowed to define and use local variables. Local variables are new to most RPG-only programmers because before the implementation of ILE and subprocedures, every variable you used was global.
If you aren't familiar with the concept of variable scope, you may not understand the difference between local and global variables. In a nutshell, scope determines when a variable is available. Global variables are those variables that are available at any time from within a program and/or group of subprocedures.
For example, you probably know about the use of counter variables for loops. Everybody has defined a numeric variable (maybe "i", or "x") to use as a counter in a loop. This technique has also undoubtedly introduced virtually untraceable bugs in your applications because you accidentally used the same variable in another portion of the program while inside of the loop.
Using local variables within subprocedures solves this problem and save hours of debugging time. You no longer have to waste time tracking down problems like the one I just mentioned because local variables are just that: local. They are defined and local to the subprocedure.
Local variables are defined by including them in the D-Spec section of the subprocedure. Because they are local to the subprocedure, you can define the same variable locally in any subprocedure. Each subprocedure then has its own "copy" of the variable. If you have a counter variable named "i" in subprocedure 1, and call subprocedure 2 which also has the same counter named "i", these two variables are independent of one another.
Until V5R2, variables defined in files (such as database files or display files) were always defined as global. It doesn't cause a problem, but in the rare cases that it may, you now have the option to define data structures local to a subprocedure, which you can use to read database records into.
First, you must define the data structure externally using the LIKEREC keyword (instead of EXTNAME). Then you can simply use the name of the data structure in the receiver variable of an input operation (READx, CHAIN, and so on). The records will be placed into a locally defined data structure. A small example is shown in Figure 1.
FFILEA IF E DISK
... Other global definitions
... Begin subprocedure
D FileADS DS LIKEREC(RFILEA)
C READ RFILEA FileADS
... End subprocedure
Figure 1 contains an example of defining a local data structure to contain a record read from FILEA
In this example, FILEA is defined for input. The record format name of FILEA is RFILEA, and this name is used in the definition of the FileADS data structure with the LIKEREC keyword. Finally, the FileADS data structure is specified in the result field of the READ operation.
When you're looking for reasons to use more subprocedures in your applications, keep in mind that the use of local variables can help eliminate problems that can occur with the use of global variables. Even if the subprocedures are only included and used in the main program, avoiding these problems is a definite advantage to using subprocedures instead of subroutines.
Pointers as Parameters
If you're familiar and comfortable with using pointers in your applications, you have probably wondered if there were any benefits to using pointers as parameters instead of simply using defined variables. The answer to this question is "yes" and "no."
If you have ever considered using a pointer as a parameter in place of defining a large string variable, determining if it would be advantageous to use a pointer instead depends on how the parameter is defined.
If you define a parameter by value by specifying the VALUE keyword, using a pointer may make the subprocedure execute faster. In other cases, the values are already passed by reference (i.e., a pointer to the storage is passed, instead of the actual value), so you don't gain much by using a pointer as the parameter.
In releases prior to V5R1, you may have had cases where you wanted to use a data structure as a parameter within a subprocedure. In these cases, using a pointer as a parameter is definitely a viable option. But the RPG compiler team in their constant improvement of RPG has added the ability to use data structures as parameters now, so even this use is becoming obsolete.
If you do choose to use pointers as parameters in your subprocedures, remember that you should never return a pointer to a value that is defined internally in your subprocedure. You shouldn't return it either as a return variable or in a subprocedure parameter.
A local variable's storage is available for the system to reclaim once the call to the subprocedure has completed. So while this technique may work from time to time, when it doesn't, you'll have a difficult time tracking down why it isn't working.
If the variables are defined as global to the module, or defined as Static, the problem should not occur as long as the function is running in the same activation group as the calling program.
With the changes and additions to the RPG compiler, using pointers as parameters is becoming unnecessary. But in those cases where you find it is needed, be sure that you are not programming unintended problems into your application.
When you define parameters for a subprocedure, you have a number of options to make the application more usable and functional. These options are defined using the OPTIONS keyword on the parameter definition in both the procedure interface and the prototype definition.
The option keywords are defined within parentheses following the OPTIONS keyword as follows:
D Variable nn OPTIONS(option_value)
The options that are currently available as follows:
*VARSIZE - This option lets you pass variables of different lengths into this subprocedure. For example, if you have a parameter defined as 100 characters and you want to use a variable defined as 20 characters as a parameter, using the *VARSIZE option lets you do it. Remember that when you use this option, you must have a way to determine the length of the data passed in. You can only work with that amount of data. (You usually pass another value containing the length or use operational descriptors.)
*NOPASS - With this option, you can specify that a particular parameter is not required to be passed on the call. All parameters following the first defined with the *NOPASS option must also be specified as *NOPASS. It is important to program for the possibility of a parameter using the *NOPASS option. Normally you can use the %parms built-in function to obtain the number of parameters that were used.
*OMIT - This option tells the compiler that the special value of *OMIT may be used as the value of the parameter, which should tell the called procedure that this particular parameter has been omitted. This option is useful if you have an option parameter, but the following parameters are required. This option is only available for parameters that are passed by reference.
*STRING - You use this option to specify that along with passing a pointer to a value, you may also use a string value. When you specify a string value in this situation, a temporary null-terminated variable is created and the pointer to this variable is used as the parameter. This option is useful in situations where you are calling APIs that use string pointers as parameters and you would rather pass a string value.
*RIGHTADJ - You use this option to automatically right adjust the value of a parameter defined with the VALUE or CONST keywords. If the subprocedure parameter is defined as a varying length, this option is not valid. However, you can use a varying length field as the value for the parameter.
Depending on your version of the operating system, some of these options may not yet be available. When determining when to use which value, be sure you understand how each one works and how it will affect your application.
Understanding the tools that are available to you as a programmer is your first step to producing proficient, manageable, and productive applications. No person can do this alone, which is why I always encourage those I speak with to take advantage of the excellent resources available online.
A good place to start would be the online ILE programming reference provided by IBM at: http://publib.boulder.ibm.com/iseries/v5r2/ic2924/books/c092508402.htm.
If something in the documentation isn't clear (and trust me, it isn't always clear), one of the best resources I can suggest is the RPG-L mailing list from Midrange.com. You can sign up for this mailing list and either just listen, or ask questions and have them answered by hundreds of the sharpest and most knowledgeable people in the industry today, most of the time within minutes.
Go to http://www.midrange.com and click on the "Lists" link to view information on one of the many topics available. You can also click on the "Archives" link to search through the hundreds of thousands of topics that have already been discussed.
Finally, another resource that I've used for many years is the IBM AS/400 newsgroup. If you have a newsreader, point it to the comp.sys.ibm.as400.misc newsgroup. If you'd rather use a web interface, Google has a usable interface to interact with the newsgroups at http://groups.google.com. The direct link to the AS/400 newsgroup is: http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&group=comp.sys.ibm.as400.misc.
These resources will definitely be a huge asset to you and your programming team whether you're looking for more information on the topics discussed here, or questions that you have for everyday issues related to the AS/400. So don't be afraid to try some of the techniques discussed here, and don't be afraid to join those involved with the lists mentioned here!
Bradley V. Stone is president of BVSTools.com, which specializes in low-cost alternative software, consulting and training for the iSeries. Bradley is also the author of many books, training manuals and articles with a focus on real world RPG programming and web enablement with RPG including the best-selling e-RPG series of books. You can reach Bradley at Stone@Lab400.com.