By Chris Malek | 03 January 2013
Application Classes and Global Variables are like Oil and Water
I recently encountered a design flaw in a delivered PeopleCode Application Class API. I thought it would be useful to share from a design point of view an example of what not to do in your application classes.
tl;dr : Do NOT ever use Global Variables in Application Class PeopleCode.
I am going to abstract away the actual bug because the API I was using was complex. That complexity would get in the way of the point I want to make about the pitfalls of using global variables in Application Classes.
So let’s say that you are given a PeopleCode API packaged in the
SUPER_API:SomeApi application class. That class gives you a method called
updateDataAsOf that takes a date as a parameter. A piece of client code may look like this.
Import SUPER_API:SomeApi; local date &effdt = %date; local &apiClass = create SUPER_API:SomeApi(); &apiClass.updateDataAsOf(&effdt);
For discussion sake let’s say that the
updateDataAsOf method takes the date parameters and does a bunch of complex setup table lookups prior to “updating the data.” This is a common scenario in an ERP system like PeopleSoft that is setup table driven.
The developer of the
SUPER_API:SomeApi class may decide that the client may call the
updateDataAsOf method several times and the setup table lookups are expensive so they should be cached in some way. The result will be a relatively slow first call to
updateDataAsOf. However, any subsequent calls would be fast because the setup values can be retrieved from the implemented caching mechanism.
So an updated client code may look something like this:
Import SUPER_API:SomeApi; local date &effdt = %date; local &apiClass = create SUPER_API:SomeApi(); &apiClass.updateDataAsOf(&effdt); /* Slower */ /* ...snip....some important processing steps .... */ /* Add a Month to the date */ &effdt = AddToDate(&effdt, 0, 1, 0); &apiClass.updateDataAsOf(&effdt); /* Faster */
Let’s imagine that the developer of the
SUPER_API:SomeApi class decides to implement the caching with a global variable. The abstracted class code may look like this.
class SomeApi method updateDataAsOf(&dateIn as date); end-class; /****EVIL CODE LIVES HERE*******/ Global Number &RateCache; /***********/ Method updateDataAsOf /+ &dateIn as Date +/ local number &rate; if &RateCache > 0 then /* User Cached value */ &rate = &RateCache; else /* No Cached Value */ /* Do expensive setup table lookups */ /* based on &dateIn param */ /* Cache value in &RateCache */ &rate = &RateCache; end-if; /* do some data updates */ end-method;
Note the use of the
Global Number &RateCache line which establishes a global variable.
- This is NOT globally scoped to the class.
- This is a global variable that is accessible to ALL code in the user’s session.
- There is so much wrong with this and it violates many OO programming best practices namely around data hiding.
- One of the key points here that can cause you tremendous grief is that this global data stays around even if you destroy the instantiated object.
- I am not aware of any destructor or finalizer language support in Application classes.
Let’s look at some example client code that would do unexpected things. In the code below we:
- Instantiate the SUPER_API:SomeApi class
- Call the UpdateDataAsOf method
- Destroy the object by setting the instance to NULL
- Instantiate a new instance of the SUPER_API:SomeApi class
- Call the UpdateDataAsOf method with a different date.
Because the implementation of SUPER_API:SomeApi uses a global variable, the first instantiated object will pollute the second instantiated object. Yikes!
Import SUPER_API:SomeApi; local date &effdt = %date; local &apiClass = create SUPER_API:SomeApi(); &apiClass.updateDataAsOf(&effdt); /* You expect this to destroy all artifacts and objects in memory */ &apiClass = null; /* ...snip....some important processing steps .... */ /* recreate the class */ local &apiClass = create SUPER_API:SomeApi(); /* Add a Month to the date */ /* Assume that the setup table data would have changed */ /* alot during this */ &effdt = AddToDate(&effdt, 0, 1, 0); /* This will produced unexpected results */ &apiClass.updateDataAsOf(&effdt);
As a developer when you set an object to NULL like this
&apiClass = null, you expect all the data that it created in memory to be destroyed and no artifacts left behind. However, the way the that the class is implemented, you would not only have to set the instantiated object to NULL. You would also have to set the global variable to NULL. This requires that the developer having an understanding of the internal workings of the class which violates another OO programming best practice.
Another pit fall of this is that you can have one instance of the class interfere with the other instance of the class. In the code below, we are going to instantiate two different instances of the SUPER_API:SomeApi class. We would expect these to operate independently of each other. However, because a global variable is used in the class implementation, the first instance will actually dictate the cache of the second instance which is very dangerous and just down right silly.
Import SUPER_API:SomeApi; local date &effdt = %date; local &apiClass_1 = create SUPER_API:SomeApi(); &apiClass_1.updateDataAsOf(&effdt); local &apiClass_2 = create SUPER_API:SomeApi(); /* 15 years ago from today */ local date &aLongTimeAgo; &aLongTimeAgo = AddToDate(%date, -15, 0, 0); /* This will produced unexpected results */ &apiClass_2.updateDataAsOf(&aLongTimeAgo);
In the code above, the
&apiClass_1 instance sets the global variable for the
&apiClass_2. This causes very strang interactions between class instances.
Please do not use Global variables in Application Classes. In Fact, if you are using global variables at all in PeopleTools you are probably doing something incorrectly.