|
This month, we're going to talk about using constant values in your LotusScript programming. While this may seem like a topic that would be exhausted after about a paragraph of discussion, you might be surprised to find that CONST values don't act like you think they do. In fact, using CONST values incorrectly can cause problems that are very hard to track down if you don't understand how they work. Let's take a look.
HOW YOU MIGHT THINK CONST VALUES WORK
There are all sorts of good reasons to use constants when you're programming. You might use them for standard messages, field names, special flags, or return codes. A common way to specify constants is with the LotusScript CONST statement in a script library, like so:
Const StatusPending = 0
Const StatusApproved = 1
Const StatusRejected = -1
If you then include the script library (with the "Use" statement) in other script libraries, agents, forms, etc. then all your code can use a common set of constants and make your coding much more consistent and much less error prone. For example, using the constants above, you can write code like:
If (doc.Status(0) = StatusPending) Then
Call SendReminderEmail(doc)
End If
This is much easier to understand, because "StatusPending" means something very specific when you read it, while "0" could mean anything. It's good coding.
THE HIDDEN PROBLEM WITH CONST
So, if this is a good way to do things, why are we still talking? Why not just make sure we're using well-defined and well-named CONST values in script libraries across all of our databases and be done with it?
Let's see what happens if one of the CONST values needs to change. As an example, we'll say that your boss decided that he absolutely hates negative numbers and wants you to change the value of StatusRejected from -1 to 2. Sounds easy, doesn't it? You change the value in your handy dandy script library and save it. Then all the agents and forms and whatnot get updated automatically, right?
Sadly, the answer is no. If all you do is change and save the script library, everything that uses that script library will still use the OLD constant value(s). Even worse, if you realize there's a problem and then use the LotusScript debugger to figure out what's going on, you'll see the NEW constant value in the debugger.
What's going on? Let's look at a few different scenarios where an agent uses a CONST value from a script library, and the value in that script library is changed:
- If the agent is run in Debug mode, the new constant is used
- If the agent is run manually or scheduled, the old constant is used (even if you just ran it in Debug mode and saw the new variable)
- If the agent is opened, saved with Ctrl-S (with no changes having been made to the agent), and closed, the old constant is still used
- If the agent is opened, modified, and saved with Ctrl-S, the new constant is used
- If the agent is recompiled using the API, the new constant is used
I don't have inside details on the LotusScript compiler, but I think the reason why the old constants stick around is because when the LotusScript is compiled, the LotusScript bytecode contains the translated value of the constant, not a reference to the constant. This is good programming as far as bytecode optimization is concerned, but bad if you change the constant in the script library, because the agent never looks back at the script library to check the value of the constant.
One point of note: If your code is already structured like this and you do need to change the CONST values, just make sure to do a "Recompile All LotusScript" in Domino Designer after you've changed the values. That will normally update the values in all the agents and design elements.
ANOTHER OPTION: GLOBAL VARIABLES
One simple thing you can do to avoid this problem is change your CONST values to global variables, and set those values in the Initialize section of the script library you declare them in. For example, you could change the CONST values in the script library above to:
Dim StatusPending As Integer
Dim StatusApproved As Integer
Dim StatusRejected As Integer
Sub Initialize
StatusPending = 0
StatusApproved = 1
StatusRejected = -1
End Sub
You can then use the values in other parts of your code exactly like you used them as CONST values, but if you need to modify them then they only have to be changed in the script library and you won't need to recompile anything else.
A downside to this technique is that the values can be modified "downstream" by any code that uses them. In other words, there's nothing stopping an agent that uses these values from changing StatusPending to 5 while it's running. Normally this isn't a concern because there's no real reason why your code should be changing the values at runtime in the first place, and the values only change in that instance of running code, but it's something to be aware of.
A BETTER OPTION: CLASSES AND SUBCLASSES
A much better option (in my mind, at least) is to use classes for your constant values. Continuing with the examples above, your script library could be written like so:
Class StatusConstants
Private constList List As Integer
Sub New
constList("StatusPending") = 0
constList("StatusApproved") = 1
constList("StatusRejected") = -1
End Sub
Property Get StatusPending As Integer
StatusPending = constList("StatusPending")
End Property
Property Get StatusApproved As Integer
StatusApproved = constList("StatusApproved")
End Property
Property Get StatusRejected As Integer
StatusRejected = constList("StatusRejected")
End Property
End Class
Dim statusConst As StatusConstants
Sub Initialize
Set statusConst = New StatusConstants
End Sub
That's a little bit more code than before, but it's virtually the same amount for the agents and design elements that use the constants. For example:
If (doc.Status(0) = statusConst.StatusPending) Then
Call SendReminderEmail(doc)
End If
With this technique, the usage of the constant values throughout your agents remains essentially the same, and the constants can't be changed arbitrarily at runtime. You also avoid any potential name collisions between script libraries or design elements that have a CONST or global variable that are named exactly the same, because you can have as many simultaneous instances of your constants class as you want (but if two script libraries each have a global variable called StatusPending, watch out).
Another big advantage of declaring your constants in classes is that it makes String constants much easier to translate into other languages. Take the following class:
Class MessageConstants
Private constList List As String
Sub New
constList("MsgWin") = "You won!"
constList("MsgLose") = "You lost."
constList("MsgTie") = "It was a tie."
End Sub
Property Get MsgWin As String
MsgWin = constList("MsgWin")
End Property
Property Get MsgLose As String
MsgLose = constList("MsgLose")
End Property
Property Get MsgTie As String
MsgTie = constList("MsgTie")
End Property
End Class
If your application needs to use French messages instead of English ones for some of your users, you can simply subclass the class like this:
Class MessageConstants_FR As MessageConstants
Sub New
constList("MsgWin") = "Vous gagnez!"
constList("MsgLose") = "Vous perdez."
constList("MsgTie") = "Ils ont fait match nul."
End Sub
End Class
And then in your agents and design elements, you could have something like:
Dim msgConst As New MessageConstants
If (profileDoc.Language(0) = "French") Then
Set msgConst = New MessageConstants_FR
End If
This way, every time you used msgConst.MsgWin later in your code, you would get the proper language string returned.
|