Guru: RDi and Refactoring
April 16, 2018 Ted Holt
When I first heard the term refactoring, I thought, “So that’s what they call it.” I had been refactoring for years, my only tools being SEU and a compiler listing. I learned a long time ago that refactoring is often necessary to enhance code, especially poorly written code. Another reason I often refactor is to better understand poor code.
To refactor means to rewrite source code without changing its external behavior. Due to all the “legacy” source code (RPG II, RPG III, fixed-form RPG IV, OCL, etc.) in IBM i shops, the ability to refactor source code is a skill that I encourage all IBM i programmers to master. Thankfully, RDi is a great help to me.
I write this article with the assumption that you see value in refactoring. If rewriting code to make it do the same thing as before makes no sense to you, I encourage you to read about refactoring on the Web. You won’t have any trouble finding relevant articles, as refactoring is taking place everywhere, not just in the world of IBM i.
In this article, I want to show you how you can use RDi to help you improve source code. To illustrate, I’ll use a very short RPG example, but imagine that it is only the relevant parts of a program of hundreds or maybe even thousands of lines of source code.
FREFACTDTIF E K DISK * C READ REFACTDT LR C *INLR DOWEQ*OFF * C CUSCLS COMP 'B' 7172 C DISCOD COMP 1 76 75 * C Z-ADD*ZERO DISPCT 32 C N75N76 GOTO SKIPD C N71N72 GOTO SKIPD C 71 Z-ADD0.08 DISPCT C 71 GOTO SKIPD C 75 QTY COMP 12 25 25 C 76 QTY COMP 24 25 25 C 75 25 COR 76 25 Z-ADD0.05 DISPCT C 75 25 COR 76 25 GOTO SKIPD C 72 Z-ADD0.04 DISPCT C SKIPD TAG C* C* C READ REFACTDT LR C ENDDO
Your first thought may be that this code is very ugly. That may be so, but it is not unlike much of the source code that is still in production in many IBM i shops. I prefer to say that the code was OK for its time and has done what needed to be done for many years. Esthetics aside, pretend that you are working on this program and that you need to understand this code so that you can modify it properly.
Since the behavior of the code is to remain the same, you need a baseline. If the program includes all appropriate test cases and produces suitable output, you have what you need. This example has nothing of the sort, so you add a temporary printer file.
FREFACTDTIF E K DISK FQSYSPRT O F 132 PRINTER * * C READ REFACTDT LR C *INLR DOWEQ*OFF * C CUSCLS COMP 'B' 7172 C DISCOD COMP 1 76 75 * C Z-ADD*ZERO DISPCT 32 C N75N76 GOTO SKIPD C N71N72 GOTO SKIPD C 71 Z-ADD0.08 DISPCT C 71 GOTO SKIPD C 75 QTY COMP 12 25 25 C 76 QTY COMP 24 25 25 C 75 25 COR 76 25 Z-ADD0.05 DISPCT C 75 25 COR 76 25 GOTO SKIPD C 72 Z-ADD0.04 DISPCT C SKIPD TAG C* C EXCPTDTL C* C READ REFACTDT LR C ENDDO OQSYSPRT E 1 DTL O KEY O CUSCLS +003 O DISCOD +003 O QTY 1 +003 O DISPCT1 +003
Running the program with comprehensive test data gives you this report.
010 A 0 1 .00 020 A 0 12 .00 030 A 0 24 .00 040 A 1 1 .08 050 A 1 12 .08 060 A 1 24 .08 070 A 2 1 .08 080 A 2 12 .08 090 A 2 24 .08 110 B 0 1 .00 120 B 0 12 .00 130 B 0 24 .00 140 B 1 1 .04 150 B 1 12 .05 160 B 1 24 .05 170 B 2 1 .04 180 B 2 12 .04 190 B 2 24 .05 210 C 0 1 .00 220 C 0 12 .00 230 C 0 24 .00 240 C 1 1 .00 250 C 1 12 .00 260 C 1 24 .00 270 C 2 1 .00 280 C 2 12 .00 290 C 2 24 .00
After you make a change to the program, you can run and compare the new report to the baseline report to be sure that you have not altered the behavior.
I’m sure you already see many places to begin to refactor. For example, you can replace this:
C 71 Z-ADD0.08 DISPCT C 71 GOTO SKIPD
with this:
C *IN71 IFEQ *ON C Z-ADD0.08 DISPCT C GOTO SKIPD C ENDIF
That’s a decent first step, but there’s much more that needs to be done. Here’s a further attempt:
C Z-ADD *ZERO DISPCT 3 2 C DISCOD CABLT 1 SKIPD C CUSCLS CABGT 'B' SKIPD C IF CUSCLS < 'B' C Z-ADD 0.08 DISPCT C GOTO SKIPD C ENDIF C IF (*IN75 AND QTY >= 12) C OR (*IN76 AND QTY >= 24) C MOVE *ON *IN25 C ELSE C MOVE *OFF *IN25 C ENDIF C IF *IN25 AND (*IN75 OR *IN76) C Z-ADD 0.05 DISPCT C GOTO SKIPD C ENDIF C IF CUSCLS = 'B' C Z-ADD 0.04 DISPCT C ENDIF C SKIPD TAG
What changed?
- The source has been converted from RPG III (a.k.a. RPG/400) to RPG IV. The Convert RPG Source (CVTRPGSRC) command handles this job easily.
- Some of the indicator references were replaced with variables. The first two GOTO’s, which were conditioned by indicators, have been replaced with CAB operations that reference variables.
- Conditioning indicators have been eliminated.
This is one way RDi can help you. The outline view shows all places where indicators and variables are used, which is necessary to know. After all, if an indicator is also changed in another part of the program, then you may not be able to replace it with a test of a variable.
A wonderful feature of refactoring is that it does not have to be done all at once. Changes can be as small as you want them to be. After each change, compare the report to the baseline report to be sure you haven’t altered the behavior of the code.
From talking to someone in sales order entry, you learn that customers are of three classes:
- A – customers who receive an 8% discount
- B – customers who receive a 4% discount
- C – customers who do not receive a discount
And that inventory items have three possible discount codes:
- 0 – no discount
- 1 – 5% discount if customer buys at least a dozen
- 2 – 5% discount if customer buys at least two dozen
The next iteration looks like this:
C Z-ADD *ZERO DISPCT 3 2 * discount code = 0 : no discount C DISCOD CABEQ 0 SKIPD * customer class = C : no discount C CUSCLS CABEQ 'C' SKIPD * customer class = A : 8% discount C IF CUSCLS = 'A' C Z-ADD 0.08 DISPCT C GOTO SKIPD C ENDIF ** discount code = 1 and qty >= 12: 5% discount ** discount code = 2 and qty >= 24: 5% discount C IF (*IN75 AND QTY >= 12) C OR (*IN76 AND QTY >= 24) C MOVE *ON *IN25 C ELSE C MOVE *OFF *IN25 C ENDIF C IF *IN25 AND (*IN75 OR *IN76) C Z-ADD 0.05 DISPCT C GOTO SKIPD C ENDIF *** customer class = B: 4% discount C IF CUSCLS = 'B' C Z-ADD 0.04 DISPCT C ENDIF C SKIPD TAG
Two or three iterations later, you finally have this:
D DisPct s 3p 2 C select C when discod = 0 C eval dispct = *zero C when cuscls = 'C' C eval dispct = *zero C when cuscls = 'A' C eval dispct = 0.08 C when (discod = 1 and qty >= 12) C or (discod = 2 and qty >= 24) C eval dispct = 0.05 C when cuscls = 'B' C eval dispct = 0.04 C other C eval dispct = *zero C endsl
It took a while, but the GOTO’s and indicators are gone, replaced by a SELECT statement that did not exist in RPG when the original code was written.
At this point, use another feature of RDi to make yet another improvement to the code, that is, rename those unpronounceable, illegible, six-characters-or-less variable names! In some cases, a simple search and replace is adequate, but at other times, search and replace can change code that does not need to be changed.
Suppose you choose to rename dispct to DiscountFactor. Highlight the variable name somewhere in the code, right-click and select Refactor, then Rename.
The fact that IBM has added a Refactor menu makes me believe that they intend to add more refactoring abilities to RDi. I certainly hope so.
The system presents another screen. I usually click the Preview button, because I like to see what the system intends to change.
Click Continue on the next panel.
Now you have a good view of what will be changed.
When you click OK, the system renames the variable.
This program gets better all the time.
I have more to say on this subject, but it will have to wait for another day. In the meantime, if you have favorite refactoring techniques that you would like to share with other readers of this august publication, please send them to me via the IT Jungle Contact page. Depending on the quality and quantity of the responses, I may publish them in a follow-up article.
Nice. And I might add that I’ve been known to refactor code I’ve just written once I see the patterns jump out at me more.