Eliminate The Legitimate Use Of GOTO
May 30, 2012 Ted Holt
Today I want to share with you some of the ugliest RPG code I’ve ever seen. It is to me the programming-language equivalent of Quasimodo, the hunchback of Notre Dame, and I am its progenitor. Then I will tell you why I wrote this code, and why, despite its ugliness, the code was correct. Last, I will tell you how to “prettify” it. The ugly code of which I speak is in a template source code member for file maintenance programs. It is similar in structure to this example: P MoveItem b D pi D inItemID 6a const D inWhsID 3a const D inFromLocID 7a const D inToLocID 7a const D inQty 5p 0 const D ouStatus 8a *** locals D AllOK c const(*zeros) D Invalid c const('1') D Active c const('A') D ItemRec ds likerec(Item: *input) inz D WhsLocRec ds likerec(WhsLoc: *input) inz D StockLocRec ds qualified inz D In likerec(StockLoc: *input) D Out likerec(StockLoc: *output) D overlay(In) /free ouStatus = AllOK; if inQty = *zero; return; endif; // open files monitor; open items; open whslocs; open stocklocs; on-error; %subst(ouStatus: 2: 1) = Invalid; /end-free C goto MoveItemExit /free endmon; // verify input parameters chain inItemID Item ItemRec; if not %found() or ItemRec.Status <> Active; %subst(ouStatus: 3: 1) = Invalid; /end-free C goto MoveItemExit /free endif; chain (inWhsID: inToLocID) WhsLoc WhsLocRec; if not %found() or WhsLocRec.Status <> Active; %subst(ouStatus: 5: 1) = Invalid; /end-free C goto MoveItemExit /free endif; // Move the stock chain (inWhsID: inFromLocID: inItemID) StockLoc StockLocRec.In; if %found() and StockLocRec.In.QtyOnHand >= inQty; StockLocRec.Out.QtyOnHand -= inQty; update StockLoc StockLocRec.Out; else; %subst(ouStatus: 6: 1) = Invalid; /end-free C goto MoveItemExit /free endif; chain (inWhsID: inToLocID: inItemID) StockLoc StockLocRec.In; if %found(); StockLocRec.Out.QtyOnHand += inQty; update StockLoc StockLocRec.Out; else; StockLocRec.Out.Status = Active; StockLocRec.Out.ItemID = inItemID; StockLocRec.Out.WhsID = inWhsID; StockLocRec.Out.WhsLocID = inToLocID; StockLocRec.Out.QtyOnHand = inQty; write StockLoc StockLocRec.Out; endif; commit; // cleanup /end-free C MoveItemExit tag /free rolbk; close items; close whslocs; close stocklocs; /end-free P e I’d say this qualifies as ugly code by anyone’s standards. The ugliest thing to me is the way it flip-flops between free format and fixed format. But then there’s that other thing: GOTO. The fact is, I needed to use GOTO, and here’s why. The routine, which started as a subroutine, but which I later converted to a subprocedure, begins with an initialization phase, continues with the main routine, and ends with a few lines of cleanup calculations. Think about that for a minute with me. The initialization phase of a routine is where you run one-time calcs that set up the routine to do whatever it does. Here you might validate the values of parameters, allocate memory, open one or more files, initialize variables, etc. The main routine (the “meat”) is where the routine fulfills its mission. The cleanup phase is where you close files, deallocate memory, load values into output-only parameters, roll back uncommitted database transactions, and such. Some routines don’t have a cleanup phase. If an error is discovered during the execution of such a routine, so that it makes no sense to continue execution of the routine, control simply returns to the caller. That is, a routine with no cleanup can have multiple exit points, which are handled with operations like RETURN and LEAVESR. But what do you do when something goes wrong in a routine that has a cleanup phase, as this one does? You GOTO. And there lies the problem. Whereas most programming languages include a GOTO command, free-format RPG does not. So one either briefly resorts to fixed-format calculations or one finds an alternative. The alternative I prefer, and which I have used with great success, is to use a status variable. Often this is an output-only parameter, but it can also be a local variable, depending on the situation. P MoveItem b D pi D inItemID 6a const D inWhsID 3a const D inFromLocID 7a const D inToLocID 7a const D inQty 5p 0 const D ouStatus 8a *** locals D AllOK c const(*zeros) D Invalid c const('1') D Active c const('A') D ItemRec ds likerec(Item: *input) inz D WhsLocRec ds likerec(WhsLoc: *input) inz D StockLocRec ds qualified inz D In likerec(StockLoc: *input) D Out likerec(StockLoc: *output) D overlay(In) /free ouStatus = AllOK; if inQty = *zero; return; endif; // open files monitor; open items; open whslocs; open stocklocs; on-error; %subst(ouStatus: 2: 1) = Invalid; endmon; // verify input parameters if ouStatus = AllOK; chain inItemID Item ItemRec; if not %found() or ItemRec.Status <> Active; %subst(ouStatus: 3: 1) = Invalid; endif; endif; if ouStatus = AllOK; chain (inWhsID: inToLocID) WhsLoc WhsLocRec; if not %found() or WhsLocRec.Status <> Active; %subst(ouStatus: 5: 1) = Invalid; endif; endif; if ouStatus = AllOK; // Move the stock chain (inWhsID: inFromLocID: inItemID) StockLoc StockLocRec.In; if %found() and StockLocRec.In.QtyOnHand >= inQty; StockLocRec.Out.QtyOnHand -= inQty; update StockLoc StockLocRec.Out; else; %subst(ouStatus: 6: 1) = Invalid; endif; endif; if ouStatus = AllOK; chain (inWhsID: inToLocID: inItemID) StockLoc StockLocRec.In; if %found(); StockLocRec.Out.QtyOnHand += inQty; update StockLoc StockLocRec.Out; else; StockLocRec.Out.Status = Active; StockLocRec.Out.ItemID = inItemID; StockLocRec.Out.WhsID = inWhsID; StockLocRec.Out.WhsLocID = inToLocID; StockLocRec.Out.QtyOnHand = inQty; write StockLoc StockLocRec.Out; endif; endif; if ouStatus = AllOK; commit; else; rolbk; endif; // cleanup close items; close whslocs; close stocklocs; /end-free P e Notice that there are five identical tests of the status code. Once the status code indicates that an error has been found, all remaining tests will automatically fail, and control will eventually reach the cleanup calcs. This is not the only way to eliminate the GOTO, but it’s a clean way.
|