The use of subprocedures in your applications should be on everyone’s “to do” list this year.
You don’t need to drop everything and rewrite your applications to take advantage of subprocedures and many other benefits that the Integrated Language Environment (ILE) offers. However, when writing new applications or performing major updates to existing components, consider utilizing these valuable yet underused technologies.
At a high level, subprocedures appear to be straightforward.
If applications have been developed to be somewhat “modular” by either calling external programs or using subprocedures to make the main line of programs easier to read and follow, subprocedures make sense.
Before you begin your first big project that incorporates subprocedures (e.g. through the use of modules or service programs), it is a good idea to learn about a few important aspects and useful tricks from people using ILE every day.
Subprocedure should have One Function and One Function Only
If you write your applications using subroutines, you may have a habit (viewed negatively by some people) of putting too much processing inside a particular subroutine.
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 only one thing.
If a subprocedure performs too many tasks, in the future, if you want to use only one part of that task, you end up with redundant processing within your subprocedures.
Suppose you write a subprocedure called #updateSales: This subprocedure could have been written to verify the sales information for a customer record when an invoice was created. Passing the customer number and sales amount into this subprocedure increases the sales totals for the customer and the associated AR records.
Later, you are modifying an AR program that performs adjustments on the AR records for a customer. You realize that your #updateSales subprocedure already performs half of the job you desire.
If you’re in a rush, you probably would copy the entire #updateSales subprocedure into an #updateAR subprocedure.
This concept works fine until updates are required to one subprocedure or the other.
Soon, you realize that this “time saving idea” made more work for you in the long run.
If you ask yourself “what should this subprocedure do” and you answer with a phrase containing a conjunction (such as “and” or “or”), you have a problem.
You have more time to split the functionality into smaller subprocedures.
What does the #updateSales subprocedure do?
It updates the sales and the AR records.
In this instance, you should have 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 referred to independently, or together.
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 — The option of passing variables to programs/subprocedures was not directly available before ILE, so you must make a choice when designing applications.
When you create ILE applications, you have the option to pass parameters by reference or by value.
The procedure interface and prototype definition of the parameter is referred to as the parameter.
When a parameter is passed by reference, only the pointer (location in memory) to that variable is passed.
The value of the calling application depends on the type of program or subprocedure.
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 taken into account in the calling program.
If the keyword value for a subprocedure parameter is not included, 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 able to be used in the calling program.
The keyword value CONST may be used to pass a variable by reference.
A major difference between using the CONST keyword and leaving the keyword blank is that the subprocedure will not be able to alter the contents of the variable.
Another important function for this keyword is that you are able to pass literal values as parameters into the subprocedure.
The keyword VALUE is used to define that this parameter is passed by value.
In this case, the parameter can be manipulated by the subprocedure, but since 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 beneficial if you want to pass literal values as parameters.
If you use subprocedures in CL programs, be aware that because CL programs cannot pass parameters by value, this option will not work with CL programs.
When you are selecting which keyword values to use, you must 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 beneficial in that the value will not be “accidentally” changed in the subprocedure.
Additionally, using CONST value allows you to pass literals and different sized variables from those defined in the prototype.
Therefore, passing a 100-character string when the parameter is defined as a 200-character string should not cause any problems.
Understanding the Scope of variables
One of the most significant advantages to using subprocedures is that you are allowed to define and use local variables.
Local variables are new to most RPG-only programmers since before the implementation of ILE and subprocedures, every variable you utilized was global.
If you are unfamiliar with the concept of variable scope, you may not comprehend the difference between local and global variables.
In essence, scope determines when a variable is available.
Global variables are those variables that are available at any time from a program and/or group of subprocedures.
For example, you may be aware of the use of counter variables for loops.
Everybody has defined a numeric variable (perhaps “i”, or “x”) to use as a counter in a loop.
This technique has also caused virtually untraceable bugs in your applications because you accidentally used the same variable in another portion of the program while inside the loop.
Using local variables in subprocedures solves this problem and saves hours of debugging time.
You no longer have to spend time tracking down problems like the one I mentioned because local variables are just that: local.
They are defined and located to the subprocedure.
Local variables are defined by incorporating them in the D-Spec section of the subproperties.
Because they are local to the subprocedure, you can define the same variable in any subprocedure.
Each subprocedure also 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 IBM I V5R2, variables in files (such as database files or display files) were always defined as global.
It does not cause a problem, however, in the rare instances that it may, you have the option to define data structures locally to a subprocedure, which you can use to read database records into.
First, you must define the data structure externally using the LIKEREC keyword (in contrast to EXTNAME).
You can simply use the name of the data structure in the receiver variable of an input operation (READx, CHAIN, etc.).
The records will be placed in a localized data structure.
When you’re looking for ways to utilize 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 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 beneficial 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 perform faster.
In other cases, the values are already passed by reference (i.e., a pointer to the storage is passed, rather than the actual value, so you don’t gain much by using a pointer as the parameter.
If you choose to use pointers as parameters in your subprocedures, be aware that you should never return a pointer to a value that is defined in your subprocedure.
It should not be returned 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 finished.
While this technique may work from time to time, when it doesn’t, you’ll have a difficult time determining 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 becomes unnecessary.
In those instances where you find it necessary, be sure that you are not implementing unintended problems into your application.
When you define parameters for a subprocedure, you have a variety of options to make the application more accessible 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.
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 method to determine the length of the data passed in. You can only work with that quantity of data. You typically 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 initial defined with the *NOPASS option must also be specified as *NOPASS. It is essential 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 utilized.
*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 parameter has been omitted. This option is beneficial if you have an option parameter, but the following parameters are required. This option is only applicable 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 referring to APIs that use string pointers as parameters and you would rather pass a string value.
*RIGHTADJ – You use this option to automatically adjust the value of a parameter defined with the VALUE or CONST keywords. If the subprocedure parameter is defined as a multiple length, this option is not valid. However, you can use a variety of length field as the value for the parameter.
Depending on your 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 functions and how it will affect your application.
Identifying the tools that are available to you as a programmer is your first step to creating proficient, manageable, and productive applications.
Start with the IBM Manual — sc092508 – Programming IBM Rational Development Studio for I ILE RPG Reference