Changing Prototypes and Dual Prototyping
May 12, 2004 Hey, Joel
You mentioned in your article “Service Programs with a Smile” that changing a procedure’s prototype could cause problems. Are you saying you should never change a prototype? I’d like to add a new parameter to a procedure, and it seems a little impractical for me to create a whole new procedure. Any suggestions?
You are absolutely right, it would be impractical, but fortunately that won’t be necessary. What I was referring to in that article was changing a parameter in a prototype, which could cause disastrous results. Adding a new parameter, however, is a different story, as long as you follow one basic guideline. Always add new parameters with options(*nopass), then check the procedure logic to see whether the parameter was passed. This way, procedure calls can continue to function without being recompiled, and you can add the new parameter to procedure calls as needed. If you want to require the new parameter, then you are back in the same boat. Here is an example:
* Original Prototype d myProc pr d parm1 10a const d parm2 5 0 const
Now let’s add a new parm and the appropriate code to check whether it was sent:
* New Prototype d myProc pr d parm1 10a const d parm2 5 0 const d parm3 10a const options(*nopass) d variable3 10a inz( 'HELLO' ) /free if %parms() >= 3 ; variable3 = parm3 ; endif ; /end-free
If parm3 is not sent, the default value HELLO, set by the D-spec, will remain. If not, variable3 will equal whatever is sent.
When a change is drastic, I definitely recommend a new procedure. However, I still don’t want to have to find all the instances of a procedure, so there are two other approaches that can be used. First–if it’s possible–change the original procedure and make it call the new procedure appropriately. Sometimes this works, depending on the individual scenario. If it does work, all the old procedure calls won’t have to be replaced–at least not immediately. Masking a procedure call this way will buy you some time, and you can slowly replace the old call with the new one.
The other method is one I came up with completely by accident when I was developing my xRPG Core Library. I call it “dual prototyping.” When I first started developing modules and service programs, I was still hung up on OPM style, which is to say that I made all my procedures in single modules and named the procedure the same as the module. (Don’t worry, I’m feeling much better now!) This approach caused a couple of headaches of its own. First of all, my procedure names tended to be cryptic. This was partly because of the 10-character name limitation, but also because I wanted to prevent a possible name collision with other people’s software. Since we don’t have qualified procedure names, you and I could each write a getName() procedure and there is no way to tell the difference between the two. Because of this name-space issue, I still use cryptic names, but I didn’t want potential users to have to call procedures like RNDTSTRUSA(). I wanted them calling getDateStringUSA() instead.
To accomplish this, I wrote the original prototype using the real procedure name, then I wrote a second prototype to rename the actual call, using the extproc option:
* Original Prototype d RNDTSTRUSA pr 50a varying d parm1 d parm2 d parm3 * Renamed call prototype d getDateStringUSA pr 50a varying extproc('RNDTSTRUSA') d parm1 d parm2 d parm3
To top it off, I put these sets of prototypes into two different /copy members, and in my programs I only copy the member with the long, non-cryptic names.
This definitely had the desired effect, but it ended up having two other unexpected benefits as well. First, this approach could alleviate the name-space issue. Any user could use this approach to create his own set of prototypes with his own naming schemes, without affecting my procedures. Second, when it came time for me to replace a procedure with a new procedure, all I had to do was simply point the renaming prototype to the new procedure name:
* Renamed call prototype d getDateStringUSA pr 50a varying extproc('RNNEWUSA') d parm1 d parm2 d parm3
Now, as programs get recompiled, they will bind the new module, rather than the old, and a slow adoption takes place. And since I try to always use const or value for my procedure parameters, I can change the parameter definitions (within reason), and the adoption will happen without the developer needing to adjust any of the procedure calls.
Joel Cochran is the director of research and development for a small software firm in Staunton, Virginia, and is the author and publisher of www.RPGNext.com. E-mail: email@example.com