Guru: RDi V9.6, Part 9 – RDi Helps with Refactoring Code into Procedures
July 6, 2020 Susan Gantner
I’m guessing that you have some RPG code in your shop that’s desperately in need of some refactoring. I have yet to visit an RPG one that wasn’t in that position. If by chance you don’t know what I mean by “refactoring” or why you may need it, I highly recommend that you read Ted Holt’s excellent series of Guru tips on refactoring RPG. I’ve included links to those tips in the Related Stories section below.
I was inspired to go back and review the entire series after I attended Ted’s session Refactoring RPG: What, Why & How at an RPG & DB2 Summit event. Both the session and the series of tips focus on practical steps anyone can take to make their code more comprehensible and easier to maintain and enhance.
In the first of those tips, Ted talked about how RDi had tools in it to help with the refactoring process. RDi’s Outline provides information that helps with his manual refactoring efforts and he also used the Refactor > Rename feature to actually implement some of the code changes.
In his second refactoring tip — Refactoring into Routines — he converted a block of code first into a subroutine and then into a subprocedure. At the time he wrote that tip, RDi didn’t have much help to offer in either of those steps. But as of V9.6.0.7, RDi has a Refactor > Extract Procedure feature. I decided to use Ted’s original refactoring example code to illustrate how it works.
Figure 1 shows the code as it was at the beginning of Ted’s second tip (already a huge improvement over the original code!) Apologies in advance for the fixed-format RPG code. More on what we can do about this later
It’s the SELECT structure that calculates a discount percentage that Ted refactored in his tip, so I’ll do the same here. Ted’s tip explains that the major decisions to be made relate to data definitions and data flow, with a goal of ensuring that the procedure doesn’t use any global data. Parameters are passed into the procedure for the values used in the calculation, and the result of the calculation is returned to the caller. Any other variables (or indeed files) that may be required in the procedure logic could be defined as local variables (or files). In this example only the discount percentage itself is needed as a local variable.
Once those decisions have been made, it’s time to let RDi help with the grunt work of creating the subprocedure. Figure 2 shows the first step — selecting the logic block destined for the subprocedure and choosing Refactor > Extract Procedure.
Next I am prompted with the RPG Procedure Wizard as shown in Figure 3.
The screen shot shows the wizard’s initial settings for this example block of code. Note that it has suggested a procedure name for me. This is no small feat, given that it has obviously figured out (correctly) that the purpose of this procedure is to get the value calculated into the dispct variable!
I have no idea exactly how the logic works to determine what the return value should be. In the limited explorations I’ve had so far, it has chosen correctly most of the time. That includes at least one occasion where no return value was suggested (and also no suggested procedure name) because there was no obvious return value in the existing code. In that case, I added a return value as a success/failure indicator although in many cases, I might decide not to bother with a return value at all. I have so far only seen one case where it chose an inappropriate return value—and I must admit that it was an unusual block of code.
I’ve found that it’s important to take note of what the process chooses as a return value — if any. When it makes the correct selection, then it can do a lot of other useful things to help you out. It will actually make some changes in the generated code to create a local version of the variable with a different name (by default) and it will then replace the old name with the new name wherever it occurs in the procedure (the equivalent of the Refactor > Rename facility).
In a moment I’ll show you how that return value support works with the SELECT block. Since that’s on a later wizard screen, there’s something more I need to do first. I need to add the three input parameters to the procedure. This is done on the first screen of the wizard.
Figure 4 shows the dialog that popped up after I hit the Add… button (which is highlighted near the bottom of Figure 3) and the entries I filled in for one of the parameters. Note that parameter keywords (including my favorite—CONST) can be specified by ticking check boxes. You must hit OK (or Enter) to go back to the initial wizard page and then press Add… again for each parameter you define.
Before leaving the initial procedure wizard page, I’m also going to rename the procedure to use the name Ted used and to add something a bit more meaningful for the “Purpose”. Figure 5 shows the final version of the first wizard page.
Now I’m ready to hit the Next button and we’ll see how the choice of return value impacts the generated code. Note that had I hit the Finish button at this point instead of Next, I would miss out on being able to modify the characteristics of my return value. Figure 6 shows the return value definition dialog initially presented by the wizard.
You notice that it suggested a name of p_dispct for my return value – based on the fact that it discovered my return value in dispct. In my experience, the prefix p_ is frequently used as a convention to designate “pointer to” Consequently if I used that name, it would confuse me every time I looked at this code in the future, especially since returning a pointer to a local value in a subprocedure is rarely a good idea. Instead I changed the generated name to the one Ted used—DiscountFactor. I’ll also manually define the type and length rather than take the wizard’s suggestion to define it like dispct, because I prefer to stay away from defining it like a global variable. The resulting panel with my changes is shown in Figure 7.
When I press Finish, the following code (Figure 8) gets generated for the procedure. Note that all earlier occurrences of dispct have been renamed to my chosen name for the return value — DiscountFactor — which has been defined as a local variable. The wizard has generated a comment block, the beginning and ending P specs, the PI and the 3 parameters.
In addition to the procedure code itself, it also generated a call to the procedure in the mainline code where the original SELECT block was located. That call is shown in Figure 9. I need to manually change the parameter names being passed in here to match the variable names used in the program for those values — CusCls, DisCod, and Qty.
It’s interesting to note that it generated the procedure call in free format even though all the other generated code—and all the other code in this source member — was in fixed format. I’m sure many of you have been wondering why all the code used here was not free format, which would obviously be my preference. Since the original example I started with was fixed format, I just stuck with that. I could have specified the option for the wizard to generate free format code (see Figure 10), but that wouldn’t have converted the original logic into free form and I didn’t like the resulting mixed format. In practice, I think the best approach would have been to convert the original code to free format before beginning this process and then take the option to have free form code generated.
You may also be wondering whether the wizard could generate a prototype and the appropriate keywords to make this usable as an external procedure. The answer to both those questions is yes. That is specified on the Options tab on the initial wizard dialog page, as shown in Figure 10 – which is the same place I could have requested free form generated code.
So there you have it. RDi’s latest venture into refactoring. As you have seen it eliminates quite a lot of detailed manual coding and will hopefully encourage many RPG developers to do more refactoring now that it is substantially automated. Even though there were still manual changes required to get the procedure to my own liking, I’m impressed with how much it can do. I suspect this is a first step and that further enhancements will be coming in the future. I’d love to see some more customization — perhaps via preferences — that I can do to deal with things like naming conventions and formatting of the code.
Despite the fact that my experience has been that it does a remarkably good job at figuring out what my procedure should be returning, I feel sure there will be occasions where I will disagree with its choice. I found it impossible to effectively change its mind on that. If I try to rename the return value — even to the name of a different variable in the procedure — it insists on renaming all occurrences of its original candidate variable to my name and returning that value anyway. (Thank goodness that undo works very nicely to allow me to start the process over!) Hopefully in the future I’ll have an option to tell it when I disagree with its decision. For now, if/when it chooses the wrong return value for me, I plan to simply tell it I don’t want a return value at all and then manually add my own code to handle the return value.
Hi Susan
It is a pity that none of the parameters described in the procedure interface are used in the procedure itself.
All variables used in CalcDiscount are global variables. So there is a hidden interface.
The proposed procedure name started with “get” which I think was a more correct name.
Warm regards
Jan