Application Classes and Global Variables are like Oil and Water
By Chris Malek | Thu, Jan 3, 2013
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
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
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
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
- 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
- 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
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
&apiClass_2. This causes very strang interactions between
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.