A Good Use for Global Variables
March 17, 2010 Ted Holt
Using global data (fields, variables and constants) is generally considered to be poor programming practice. One reason is that a subroutine or subprocedure that uses global data is not easily placed into service elsewhere. Another reason is that undesirable side effects may occur when one routine changes a variable that another routine uses. However, in some situations global data makes sense. I would like to provide one example. Assume a service program with several subprocedures. The source code for the module from which the service program is built looks like this: H nomain D/copy prototypes,mysvcpgm P DoThing1 b export D pi D Parm1 24a varying const ... more code ... P e P DoThing2 b export D pi D Parm1 12a varying D Parm2 9p 0 D Parm3 5a ... more code ... P e P DoThing3 b export D pi D Parm1 24a D Parm2 48a ... more code ... P e ... more subprocedures ... P DoThingN b export D pi D Parm1 4a ... more code ... P e The module consists of N (i.e., some number of) related subprocedures that work together to do something (e.g., pricing, costing, shipping, posting, etc.). Suppose we wish to be able to control these routines in some way. For example, we wish to make them able to use either a 12- or 13-period accounting year. Or we want to make them produce printed output or not. How would we go about controlling the routines? One way would be to add a parameter to each subprocedure. Let’s say we want the subprocedures to produce a program dump when something goes wrong. Each subprocedure gets a new parameter. Let’s call the new parameter ParmDump. H nomain D/copy prototypes,mysvcpgm P DoThing1 b export D pi D Parm1 24a varying const D ParmDump n const ... more code ... /free ... more code ... monitor; ... more code ... on-error; if ParmDump; dump(a); endif; endmon; ... more code ... /end-free P e P DoThing2 b export D pi D Parm1 12a varying D Parm2 9p 0 D Parm3 5a D ParmDump n const ... more code ... /free ... more code ... if SomethingWentWrong; if ParmDump; dump(a); endif; endif; ... more code ... /end-free P e P DoThing3 b export D pi D Parm1 24a D Parm2 48a D ParmDump n const ... more code ... /free ... more code ... if not %found(SomeFile); if ParmDump; dump(a); endif; endif; ... more code ... /end-free P e ... more subprocedures ... P DoThingN b export D pi D Parm1 4a D ParmDump n const ... more code ... /free ... more code ... if Balance < *zero and ParmDump; dump(a); endif; ... more code ... /end-free P e Each routine has been changed to produce a program dump if something goes wrong and ParmDump is on. Now we must change all the calls in the caller(s) to pass another parameter. D DumpOnError n /free ... more code ... if SomeCondition; DumpOnError = *on; Endif; ... more code ... DoThing1 (SomeData: DumpOnError); The extra parameter in the call to DoThing1 is DumpOnError, which the caller must turn on in order to make the service program produce dumps. What sets SomeCondition? Whatever you want. I’ll come back to that in a bit. Here’s another approach that’s less burdensome. Rather than add a dump parameter to all N subprocedures, let’s add a global variable to the service program to indicate whether errors are to cause dumps or not. Let’s add code to each subprocedure to generate a dump when an error is found and DumpEnabled is on. H nomain D/copy prototypes,mysvcpgm D DumpEnabled s n inz(*Off) P DoThing1 b export D pi D Parm1 24a varying const ... more code ... /free ... more code ... monitor; ... more code ... on-error; if DumpEnabled; dump(a); endif; endmon; ... more code ... /end-free P e ... etc. ... Now let’s add a subprocedure to control the global DumpEnabled variable. P SetDump b export D pi D inSetDump n const /free eval DumpEnabled = inSetDump; /end-free P e The global variable is DumpEnabled. Notice that it precedes the first subprocedure definition. Now we modify the calling routine(s), like this: /free ... more code ... if SomeCondition; SetDump (*on); Endif; ... more code ... DoThing1 (SomeData); Now all subprocedures create dumps when an error arises. There are plenty of ways to control the condition in the calling routine(s). For example, you might have calling programs check a certain position of a data area. Normally the data area would have a blank in that position, and dumps would not be produced. If the data area contains a D in that position, the caller executes SetDump with an argument of *ON. To control the condition, use the Change Data Area (CHGDTAARA) command. CHGDTAARA DTAARA(SOMELIB/CONTROL (5 1)) VALUE('D') In a situation where a group of routines needs common data, global data provides a simple and effective way to make sure that all subprocedures work together.
|