Continuing in this series on subprocedure parameters, the MESSAGES_getMessage subprocedure is shown again:
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.
In this article, I'll describe my naming convention for parameters and review the use of the OPTIONS keyword, with respect to coding optional procedure parameters.
The parameter naming convention I use is pretty obvious: start a parameter name with the characters p_. I use this parameter name prefix so that it is obvious when I use a parameter in the subprocedure code. The rest of the name is spelled out, using complete words (in most cases). I use the D-spec continuation option for long names so that I don't have to be concerned with fitting the name into the 14 character space that is normally allowed on the D-spec.
The ILE RPG Reference manual points out that a parameter name is not required in the subprocedure prototype. You only need to supply a parameter name on the actual procedure interface (that is, the code that is at the beginning of the actual subprocedure).
I can't imagine any possible reason for not using the parameter name in the prototype. (I know that someone will offer the tired excuse of needing to "protect" their code for some reason.) I consider the parameter name to be a required entry in the subprocedure prototype. If you ever encounter a subprocedure prototype that you will be using that does not have parameter names, you must stop whatever you are doing, find out what the names should be, and edit the prototype source to include the names.
The ILE RPG Reference lists several special values that you can include in a parameter OPTIONS statement. In most of my subprocedures, I use only the two options shown in the example above: *NOPASS and *OMIT. You can specify the options in either order, using upper or lowercase. Note that the colon character is used as a parameter separator.
By using *NOPASS, I can call the procedure and simply not pass parameter values that are not needed:
msg1 = MESSAGES_getMessage('MSG0928');
If I didn't have the *NOPASS option for the second, third and fourth parameters, I would have to code the call like this:
msg1 = MESSAGES_getMessage('MSG0928' : ' ' : ' ' : ' ');
Because it is awkward, ugly and error prone to have to supply parameter values when they aren't needed, you should use *NOPASS for all parameters that you can define as optional parameters. In some subprocedures you'll need to pass all of the parameters every time, so you can't use *NOPASS in those parameter lists. In subprocedures where you can use *NOPASS, keep in mind that every parameter following the first *NOPASS must also be *NOPASS.
As discussed in this article, parameter order is usually the biggest factor you have to take into account when defining a parameter list that supports *NOPASS parameters. Almost by definition, a *NOPASS parameter will have a default value or you will provide a way in your code to set a value for the parameter if it is not passed.
If you're just getting started with subprocedures, it can be difficult to create procedures with default values. Sometimes it's hard to imagine which parameters can be meaningfully use defaults, and which will always require a value from the caller, at the point of the call. That is why the best thing to do is to start creating and using as many subprocedures as possible with the *NOPASS option, so that you'll learn where to look for *NOPASS opportunities and how to apply them.
Practically speaking, you'll need to write more code to handle the *NOPASS parameters and their defaults. If you're new to this type of coding, you'll probably completely abandon it when you see the type of code I write to handle a *NOPASS parameter and its default. Even if you don't like my code, I suggest that you consider the principle behind it. When you create subprocedures, it is worth putting the extra effort into coding so that you have as much run-time flexibility as possible. You can't envision all of the ways your subprocedures might be used in the future, but it is safe to predict that if a subprocedure is easy to use, the effort put into its development will be well repaid.
There is one problem with using optional parameters, and that is when there are additional parameters following an optional parameter. For example, if I want to get a message with no substitution data, but from a different message file than the default, I might code the call like this:
msg1 = MESSAGES_getMessage('MSG0928' : ' ' : 'NEWMSGF');
In this call, the first parameter (message ID) is required. The third parameter is used to specify the message file to use. The question is, what value if any should be passed for the second parameter (substitution data), since it is not needed for this message?
The example shows one way of solving the problem: just pass a blank as the second parameter. However, that does not clearly convey the intention of the call. Is message id MSG0928 defined with a character data substitution value, whose value is the single blank? Or is message id MSG0928 defined with no substitution data, in which case the second parameter is meaningless? If you wrote the line of code shown above without using the *OMIT parameter option, you would have to use the Display Message Description (DSPMSGD) command to display the message description and look for its substitution data definition to get the answer to those questions.
When you include the *OMIT option for a parameter, you can code the call like this:
msg1 = MESSAGES_getMessage('MSG0928' : *OMIT : 'NEWMSGF');
In this call, it is obvious that there is no substitution data for the message.
You might argue that if the parameter list order were changed, you could put the message file name as the second parameter, and then you wouldn't need to worry about the omitting substitution data. It is certainly true that the order of parameters affects how you will be able to call the procedure, so you'll want to pay careful attention to the definition. Sometimes it won't be clear which parameter list order is best, so you should create a version of the subprocedure as soon as possible and start using it. If you find that you are using the same value on repeated calls to the subprocedure, you might be able to code those parameters as optional parameters, using *NOPASS and *OMIT.
You may have noticed that the fourth and last parameter (p_messageLibrary) was also defined with *NOPASS and *OMIT. The question is, are those options needed on the last parameter?
In fact, the *NOPASS option is required, as there are preceding parameters that are defined as *NOPASS.
The *OMIT option is not required on the last parameter by the compiler. I include it simply so that I process all of the optional parameters the same. Although it is unlikely that I would call the subprocedure and code *OMIT for the fourth parameter, my code supports the use of the special value. If necessary, I can have code in my subprocedure that checks the fourth parameter and handles it differently, based on whether it was not passed or if it was passed with the *OMIT value.
In the next article, you'll see how to determine if a parameter was passed and if the value that was passed is the special value *OMIT.
Craig Pelkie
October 31, 2004
Copyright © 2004, 2005, Craig Pelkie, ALL RIGHTS RESERVED