Subprocedure Parameters, part 2 - how a parameter is passed

In Subprocedure Parameters, part 1, I presented the MESSAGES_getMessage subprocedure. Its prototype is shown here for review:

D MESSAGES_getMessage...
D                 pr                  likeds(MESSAGES_Message)
D   p_messageID...
D                                     like(Types.MSGID)
D                                     const
D   p_substData...
D                                     like(Types.CHAR512)
D                                     const
D                                     options(*nopass : *omit)
D   p_messageFile...
D                                     like(Types.NAME)
D                                     const
D                                     options(*nopass : *omit)
D   p_messageLibrary...
D                                     like(Types.NAME)
D                                     const
D                                     options(*nopass : *omit)
Figure 1: the MESSAGES_getMessage subprocedure prototype.

Before moving on with a description of how to actually code and use optional parameters, I will review how parameters are passed. There are three distinct techniques you can use to pass a parameter from a caller to a subprocedure:

The way you indicate which type of parameter passing technique you want to use is by the use or non-use of a keyword:

Parameter passing technique Keyword for parameter
Reference (no keyword, reference is the default)
Value VALUE or value
Read-only reference CONST or const

In some cases, the way you will pass a parameter is dictated by what you are passing it to. For example, if you create procedure prototypes to call programs (and many of the system APIs), you need to pass parameters by reference. The program or API can then change the value that was passed to it.

Some APIs require that you pass parameters by value or by read-only reference. The only way to be sure is to locate, read and understand the documentation for the API.

As shown in the sample prototype above, I routinely define and pass parameters as read-only reference, using the const keyword. That means that I cannot change the parameter value in the subprocedure. That means that I don't return values back to the caller through the parameter list.

Pass by read-only reference

Most RPG programmers can't imagine why anybody would code a parameter list that can't be modified. After all, the basis of program calls (CALL/PARM) is that you can pass parameters to a program, change the parameter values in the called program and have the changes returned to the caller. When that was the only available technique, that was what you had to work with.

Perhaps the most beneficial advantage of subprocedure parameters over program call PARM lists is that you can, in fact, lock-down the parameter values. The scenario I want to prevent is a deeply nested procedure call stack, where the procedures are free to change parameter values. If a value is erroneously changed at some point in the call stack, it can be difficult to impossible to locate and repair the bug. You would need to recreate the circumstances that lead to the particular call stack, with the same values being passed and returned to replicate the error. You would then have to find the bug, understand it, correct it and verify that the correction works. By coding my procedures with unchangeable parameters, I instantly eliminate an entire class of bug-creating and bug-hiding code.

Although you can protect your parameters by passing them by value, I go the next step and pass parameters as read-only reference. This requires further explanation. I have now coded a procedure that not only does not return changed parameter values to the caller, it also does not allow the procedure to change parameter values internally.

It might help if you think of procedure parameters as being similar to legal documents. The only reason to pass a value to a procedure in a parameter is because you want to somehow influence the activity of the procedure. As such, each parameter that you pass to a procedure is important. Not only is it assigned a name to indicate what it represents, but it also contains a certain type of data and the value itself.

Now think of how you treat important legal documents that you have, for example, transcripts, financial papers, your marriage license and others. You've probably found, over the years, that it is generally best to make copies of those documents and make any notations that need to be made on the copies. That is because it may be difficult to impossible to recreate the original document in its original format. The cost of making and using a copy is minimal, compared to the cost of trying to repair or recover a damaged original.

In terms of a procedure parameter, that means I always have the original value that was passed from the caller. If necessary, I can refer to the parameter name at any point in the procedure, confident that I am accessing the value that was passed.

In my code, I make a sharp delineation between what the caller passes to my procedure and what the procedure does internally. If I preserve and cannot alter the values that are passed to me, it is much easier to resolve disputes when there are errors. I can confidently add logging code to my procedures to log the values of passed parameters, knowing that the values that are passed cannot be changed. When there are troubles and failures, I simply start logging, run to the point of failure, then dump the logs. The parameter values are there for inspection, in their unaltered state. If the caller did pass valid values, then the problem is obviously in the procedure itself. On the other hand, if the values from the caller are incorrect, I can direct my debugging attention back to the source of the problem.

The point I keep in mind while coding is that I am not trying to force myself into arbitrary, difficult to use rules just for the sake of some malformed notion of it being "good for me". The point is that I enlist the compiler's rules to help me structure my code in a way that makes it impossible to create certain types of bugs. My default parameter passing convention is to use the CONST keyword, to pass parameters by read-only reference. I only pass by reference or by value if required to by the called program or function.

Performance coda

The ILE RPG Programmer's Guide describes the parameter passing options, with the obligatory mentions of "performance". The issue seems to be that when you pass "large character fields", it is more efficient to pass parameters by reference than by value. Passing by read-only reference would seem to render this a moot point, with the caveat that "the compiler may copy the parameter to a temporary field [when using read-only reference]".

Personally, I never consider performance issues as being a determinant of whether or not I should use a particular coding construct. There are hundreds of ways to improve program performance, practically all of which are easier and much more cost efficient than debugging. When the choice is between using a coding construct that will help prevent bugs, and one that a manual says "may" improve run-time performance, I will always choose the bug prevention technique. I've simply found over the years that bugs take up far more of my time than I have ever been able to save by sacrificing better coding practices to performance.

Craig Pelkie
October 31, 2004