After defining the subprocedure name, the next critical task is defining its parameters. Most subprocedures that you code will use one or more parameters. It is perfectly valid to create a subprocedure that has no parameters, although you will only occasionally code a no-parameter procedure.
Depending upon how you define the parameter list, the subprocedure can be easy to use in a variety of circumstances or it may be restricted to one specific way of calling it. For subprocedures that are meant to be used by a module's external clients, it is usually best to put the effort into making the subprocedure easily used, rather than forcing callers to use it in one specific way.
For example, the MESSAGES_getMessage subprocedure retrieves a message from a system message file. You can pass message substitution data to the subprocedure, with the result being a formatted message returned with the substitution data values. The prototype for the subprocedure is this:
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 parameter list, as defined in the production version of this subprocedure.
There are four parameters passed to the subprocedure:
Based on this prototype, the only required parameter is the first (the message ID). It is possible to call the subprocedure and pass it just a message ID, with the expectation that the subprocedure will retrieve a message from a message file that was previously set. In later articles in this series, I'll show you some techniques to assign default values, so that you can create subprocedures like this. For now, I need to describe why I coded the parameter list as I did.
If you're not concerned with providing optional parameters, you can simply code the parameter list in whatever order makes the most sense. For example, if I hadn't planned to use optional parameters with the MESSAGES_getMessage subprocedure, I might have coded its parameter list like this:
D MESSAGES_getMessage...
D pr likeds(MESSAGES_Message)
D p_messageLibrary...
D like(Types.NAME)
D const
D p_messageFile...
D like(Types.NAME)
D const
D p_messageID...
D like(Types.MSGID)
D const
D p_substData...
D like(Types.CHAR512)
D const
D options(*nopass)Figure 2: the parameter list, as it might have been coded on the first attempt.
In this example, I've made a small concession. The last parameter (substitution data) does not need to be passed, as there will be many times when I want to retrieve a message that has no substitution data.
The order of the parameters in this parameter list is "OS/400 centric", in that it passes the library name first, then the message file name, then the message ID. This fits in well with the OS/400 convention of library/object references, followed by additional data about the object (in this case, the message ID). However, by coding the parameter list like this, I'm now locked in to providing at least three parameters to the subprocedure on each call.
That probably does not seem like a big deal to you, and if the subprocedure were used only occasionally, it would not be necessary to spend any more time thinking about its parameter list. In fact, I use this subprocedure a lot in my programs, particularly during program initialization when I may be retrieving dozens of messages to to use for labels and headings. If I hadn't thought about how I wanted to use the subprocedure, and coded the parameter list with the library name as the first parameter, I'd have many calls looking like this:
msg1 = MESSAGES_getMessage(MSGFLIB : MSGFNAME : 'MSG0123');
msg2 = MESSAGES_getMessage(MSGFLIB : MSGFNAME : 'MSG0456');
msg3 = MESSAGES_getMessage(MSGFLIB : MSGFNAME : 'MSG0901');
msg4 = MESSAGES_getMessage(MSGFLIB : MSGFNAME : 'MSG1234');
Worse, if I hadn't assigned the message library or message file name to constants or variables, I might have statements like this:
msg1 = MESSAGES_getMessage('MSGFLIB' : 'MSGFNAME' : 'MSG0123');
msg2 = MESSAGES_getMessage('MSGFLIB' : 'MSGFNAME' : 'MSG0456');
msg3 = MESSAGES_getMessage('MSGFLIB' : 'MSFGNAME' : 'MSG0901');
msg4 = MESSAGES_getMessage('MSGFLIB' : 'MSGFNAME' : 'MSG1234');
In these statements, I've hardcoded literals for the message library and message file, which is not an uncommon practice in RPG programs.
You may have noticed the "typo" in the message file name in the msg3 statement (MSFGNAME instead of the intended MSGFNAME). One thing is for certain, the compiler won't complain about quoted literal typos. The problem is further compounded if you don't use the value of msg3 until some later point in the program. For example, that message might be a warning or error message. Upon encountering the warning or error condition, your program would display the value of msg3, which may have been returned as blank from the subprocedure, due to the error in the message file name.
Typos are usually the most difficult of all errors to find. Unlike other bugs, there is almost never any sense of elation when you ultimately find a typo, more likely, just a grim snarl of annoyance over being bitten, yet again. One way to minimize the effect of typos is to use constants or variables whenever you can, rather than quoted values, as shown in the first sequence of calls.
One way to minimize the opportunity for parameter errors is to make the parameters optional. For example, if I use the first prototype (with message ID as the first parameter), I can code my calls like this:
msg1 = MESSAGES_getMessage('MSG0123');
msg2 = MESSAGES_getMessage('MSG0456');
msg3 = MESSAGES_getMessage('MSG0901');
msg4 = MESSAGES_getMessage('MSG1234');
These statements imply that the name of the message file and its library have been set elsewhere. I'll show you the technique I use later. For now, I am simply bringing to your attention that the parameter list order has a direct and consequential impact on how you use a subprocedure.
When I defined the MESSAGES_getMessage subprocedure, I thought of the parameter list in these terms:
The reason for considering the first item is so that you will include all of the parameters that you need. For example, I initially coded the subprocedure without the substitution data parameter. After using the procedure for a while in development, I found it would be useful to be able to work with message substitution data. Rather than code another subprocedure, I was able to revise the parameter list to include the substitution data. You won't always be able to rearrange a parameter list, especially if you've already put a subprocedure into production. In that case, you might have to code another subprocedure to include the parameters that were not in the first version. But if you can catch these types of changes before putting code into production, you should go back and make the change. If you need to change other code that is in development that uses the original version of the parameter, make the change. It is a lot better to take the hit now, in development, than deploy a procedure that limps along because you didn't want to be bothered to revise it when it was easiest to do.
The second item, how frequently does a parameter vary, leads to the parameter list order shown in Figure 1. In my applications, I usually have one message file. If I set the message file name and library, those values won't change for the duration of the program. What will change, on almost every call to the subprocedure, is the message ID I want to retrieve and its substitution data, if any. Because I factored in my intentions of how I would use the procedure, I arranged the parameter list so that the most frequently varying parameter, the message ID, is first. After that, the remaining parameters are optional.
As long as you're going to be putting some effort into thinking about the parameter list, you need to think about how many parameters you'll be passing. ILE RPG is surprisingly generous with the number of parameters you can pass: 399 (source: ILE RPG Reference, Appendix A, "RPG IV Restrictions"). Because the lower limit is zero, you have a wide range to work with.
In addition to what you may have heard in Psych 101, there has been some research done with software developers (documented in the book Code Complete ) that indicates that the optimal number of parameters is seven or less. In this case, the issue is not so much that you will "remember" the parameters (as in the Psych 101, "people can remember up to 7 things at once"). There is really no need to ever have to remember procedure parameters: you simply look them up. Over time, after you've use the procedure several hundred times, you might be able to remember the parameters. Until then, look them up. If you don't, the compiler will happily look up the parameters for you at compile time, and give you a complete report of your errors.
The issue with the number of parameters is that when you use a lot of parameters, you greatly increase the dependencies of the parameters on each other. It gets to be really difficult to get all of the parameter values set so that they are valid, given earlier values. Think of how the OS/400 command prompter exposes or hides optional parameters, based upon earlier values in the parameter list. You don't have a command prompter like that when you're coding subprocedure calls, so you need to know about all of the parameters in the list.
When you have long parameter lists, you may be doing too much in the subprocedure. You should seriously consider splitting the subprocedure into two or more procedures, if it makes sense.
Another alternative that you should consider is that it might make sense to group parameters together into one or more data structures, and pass the data structures as parameters. That way, you can at least indicate how you intend the parameters to be used, assuming that you put some effort into organizing the data structures.
If you need more than seven parameters, then by all means, code away. I'm simply suggesting that you should start seeing warning flags waving and little mental rockets going up when you start going over seven. Think about what you're doing! There's no benefit at all in trying to cram too much into one procedure, and burdening it with a lengthy parameter list for all of its life. When you have an assortment of procedures that use short, to-the-point parameter lists, you'll find it much easier to combine those procedures in many combinations, rather than trying to force fit awkwardly long parameter lists into your programs.
Craig Pelkie
October 31, 2004
Copyright © 2004, 2005, Craig Pelkie, ALL RIGHTS RESERVED